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 9df066a3d594aeeca41677744f0f29a81901f124
parent 86dd70c41a66c874b49092caa1986ecedd36f254
Author: lumidify <nobody@lumidify.org>
Date:   Sat, 23 Oct 2021 19:39:17 +0200

Minor refactoring

Diffstat:
MIDEAS | 3++-
MLICENSE | 2++
MMakefile | 35+++++++++++++++++++++++++++++++++--
AREADME | 1+
Aaction.h | 9+++++++++
Mbuffer.c | 751++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mbuffer.h | 109+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mcache.c | 104+++++++++++++++++++++++++++++++++----------------------------------------------
Mcache.h | 17++++++++++++-----
Dcommands.c | 168-------------------------------------------------------------------------------
Dcommands.h | 1-
Mcommon.h | 39++++++++++-----------------------------
Akeys.c | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Akeys.h | 23+++++++++++++++++++++++
Akeys_basic.c | 937+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Akeys_basic.h | 1+
Akeys_basic_config.h | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Akeys_command.c | 325+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Akeys_command.h | 15+++++++++++++++
Akeys_command_config.h | 41+++++++++++++++++++++++++++++++++++++++++
Dlbuf.c | 51---------------------------------------------------
Dlbuf.h | 12------------
Mledit.c | 1840++++---------------------------------------------------------------------------
Msearch.c | 42+++++++++++++++++++++++++-----------------
Atheme.c | 35+++++++++++++++++++++++++++++++++++
Atheme.h | 16++++++++++++++++
Atheme_config.h | 9+++++++++
Atxtbuf.c | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Atxtbuf.h | 11+++++++++++
Mundo.c | 213+++++++++++++++++++++++++++++++++++++------------------------------------------
Mundo.h | 36+++++++++++++++++++++++-------------
Mutil.c | 17++++++++++-------
Mutil.h | 4++--
Awindow.c | 769+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awindow.h | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
35 files changed, 3503 insertions(+), 2477 deletions(-)

diff --git a/IDEAS b/IDEAS @@ -1,4 +1,5 @@ +* Important: use less memory (maybe don't keep all pango layouts around + all the time) * allow editing same file in multiple places at same time (like in acme) * add different (more basic) text backend -* visual selection mode - allow to switch cursor between selection ends * https://drewdevault.com/2021/06/27/You-cant-capture-the-nuance.html diff --git a/LICENSE b/LICENSE @@ -1,3 +1,5 @@ +Note: Some stuff is stolen from st (https://st.suckless.org) + ISC License Copyright (c) 2021 lumidify <nobody@lumidify.org> diff --git a/Makefile b/Makefile @@ -9,8 +9,39 @@ MANPREFIX = ${PREFIX}/man BIN = ${NAME} MAN1 = ${BIN:=.1} -OBJ = ${BIN:=.o} cache.o buffer.o memory.o util.o search.o lbuf.o undo.o commands.o -HDR = cache.h buffer.h memory.h common.h util.h search.h lbuf.h undo.h commands.h +OBJ = \ + buffer.o \ + cache.o \ + keys.o \ + keys_basic.o \ + keys_command.o \ + ledit.o \ + memory.o \ + search.o \ + theme.o \ + txtbuf.o \ + undo.o \ + util.o \ + window.o + +HDR = \ + action.h \ + buffer.h \ + cache.h \ + common.h \ + keys.h \ + keys_basic.h \ + keys_basic_config.h \ + keys_command.h \ + keys_command_config.h \ + memory.h \ + search.h \ + theme.h \ + theme_config.h \ + txtbuf.h \ + undo.h \ + util.h \ + window.h CFLAGS_LEDIT = -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 diff --git a/README b/README @@ -0,0 +1 @@ +Work in progress. Nothing to see here. diff --git a/action.h b/action.h @@ -0,0 +1,9 @@ +enum action_type { + ACTION_NONE, /* pass next key to basic key handler */ + ACTION_GRABKEY /* pass next key to given callback */ +}; + +struct action { + enum action_type type; + struct action (*callback)(ledit_buffer *buffer, XEvent *event, int lang_index); +}; diff --git a/buffer.c b/buffer.c @@ -2,21 +2,25 @@ /* FIXME: also cache PangoLayouts since keeping them around isn't really of much use? */ #include <stdio.h> +#include <errno.h> #include <string.h> #include <assert.h> #include <limits.h> #include <X11/Xlib.h> #include <X11/Xutil.h> +#include <X11/Xatom.h> #include <pango/pangoxft.h> #include <X11/extensions/Xdbe.h> #include "memory.h" #include "common.h" -#include "lbuf.h" -#include "buffer.h" -#include "cache.h" +#include "txtbuf.h" #include "undo.h" +#include "cache.h" +#include "theme.h" +#include "window.h" +#include "buffer.h" /* * Important notes: @@ -30,25 +34,43 @@ static PangoAttrList *basic_attrs = NULL; +static void err_text_dirty(ledit_buffer *buffer, int line); +static void move_text_gap(ledit_line *line, int index); +static void resize_and_move_text_gap(ledit_line *line, int min_size, int index); +static char *strchr_len(char *text, char c, long len); static void init_line(ledit_buffer *buffer, ledit_line *line); -/*static void delete_line_section(ledit_buffer *buffer, int line, int start, int length);*/ static void delete_line_section_base(ledit_buffer *buffer, int line, int start, int length); +static void normalize_and_set_pango_text(ledit_line *line); +static void swap(int *a, int *b); +static void sort_selection(int *line1, int *byte1, int *line2, int *byte2); +static void copy_selection_to_x_primary(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2); + +void +ledit_buffer_set_mode(ledit_buffer *buffer, enum ledit_mode mode) { + buffer->common->mode = mode; + ledit_window_set_mode(buffer->window, mode); + ledit_change_mode_group(buffer->undo); +} /* FIXME: destroy basic_attrs somewhere */ ledit_buffer * -ledit_create_buffer(ledit_common_state *state) { +ledit_buffer_create(ledit_common *common, ledit_theme *theme, ledit_window *window) { if (basic_attrs == NULL) { basic_attrs = pango_attr_list_new(); + #if PANGO_VERSION_CHECK(1, 44, 0) PangoAttribute *no_hyphens = pango_attr_insert_hyphens_new(FALSE); pango_attr_list_insert(basic_attrs, no_hyphens); + #endif } ledit_buffer *buffer = ledit_malloc(sizeof(ledit_buffer)); - buffer->state = state; + buffer->common = common; + buffer->window = window; buffer->lines = NULL; buffer->filename = NULL; buffer->lines_num = 0; buffer->lines_cap = 0; + buffer->selecting = 0; buffer->cur_line = 0; buffer->cur_index = 0; /* FIXME: trailing currently not used */ @@ -59,8 +81,13 @@ ledit_create_buffer(ledit_common_state *state) { buffer->display_offset = 0; buffer->sel.line1 = buffer->sel.byte1 = -1; buffer->sel.line2 = buffer->sel.byte2 = -1; - ledit_append_line_base(buffer, -1, -1); - ledit_recalc_all_lines(buffer); + ledit_buffer_append_line_base(buffer, -1, -1); + ledit_buffer_recalc_all_lines(buffer); + buffer->cache = ledit_cache_create(common); + buffer->undo = ledit_undo_stack_create(); + buffer->theme = theme; + ledit_window_set_scroll_callback(window, &ledit_buffer_scroll_handler, buffer); + ledit_window_set_button_callback(window, &ledit_buffer_button_handler, buffer); return buffer; } @@ -68,7 +95,7 @@ ledit_create_buffer(ledit_common_state *state) { /* FIXME: don't generate extra blank line at end! */ /* WARNING: errstr must be copied as soon as possible! */ int -ledit_load_file_into_buffer(ledit_buffer *buffer, char *filename, int line, char **errstr) { +ledit_buffer_load_file(ledit_buffer *buffer, char *filename, int line, char **errstr) { long len; int off = 0; ledit_line *ll; @@ -82,7 +109,7 @@ ledit_load_file_into_buffer(ledit_buffer *buffer, char *filename, int line, char if (len < 0) goto errorclose; if (fseek(file, 0, SEEK_SET)) goto errorclose; - ll = ledit_get_line(buffer, line); + ll = ledit_buffer_get_line(buffer, line); file_contents = ledit_malloc(len + 2); /* mimic nvi (or at least the openbsd version) - if the line is empty, insert directly, otherwise insert after the line */ @@ -101,7 +128,7 @@ ledit_load_file_into_buffer(ledit_buffer *buffer, char *filename, int line, char } if (fclose(file)) goto error; - ledit_insert_text_with_newlines( + ledit_buffer_insert_text_with_newlines( buffer, line, ll->len, file_contents, len + off, NULL, NULL ); free(file_contents); @@ -119,15 +146,15 @@ errorclose: /* FIXME: allow to write only certain lines */ int -ledit_write_buffer_to_file(ledit_buffer *buffer, char *filename, char **errstr) { +ledit_buffer_write_to_file(ledit_buffer *buffer, char *filename, char **errstr) { FILE *file; ledit_line *ll; file = fopen(filename, "w"); if (!file) goto error; clearerr(file); for (int i = 0; i < buffer->lines_num; i++) { - ll = ledit_get_line(buffer, i); - ledit_normalize_line(ll); + ll = ledit_buffer_get_line(buffer, i); + ledit_buffer_normalize_line(ll); if (fprintf(file, "%s\n", ll->text) < 0) goto errorclose; } if (fclose(file)) goto error; @@ -144,20 +171,22 @@ errorclose: } void -ledit_destroy_buffer(ledit_buffer *buffer) { +ledit_buffer_destroy(ledit_buffer *buffer) { ledit_line *l; for (int i = 0; i < buffer->lines_num; i++) { - l = ledit_get_line(buffer, i); + l = ledit_buffer_get_line(buffer, i); g_object_unref(l->layout); free(l->text); } + ledit_cache_destroy(buffer->cache); + ledit_undo_stack_destroy(buffer->undo); free(buffer->lines); if (buffer->filename) free(buffer->filename); free(buffer); } void -ledit_normalize_line(ledit_line *line) { +ledit_buffer_normalize_line(ledit_line *line) { if (line->gap < line->len) { memmove( line->text + line->gap, @@ -177,12 +206,12 @@ err_text_dirty(ledit_buffer *buffer, int line) { "WARNING: Line had text_dirty or h_dirty attribute " "set when rendering. Fix your code!\n" ); - ledit_recalc_from_line(buffer, line); + ledit_buffer_recalc_from_line(buffer, line); } void -ledit_set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end_byte) { - ledit_line *l = ledit_get_line(buffer, line); +ledit_buffer_set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end_byte) { + ledit_line *l = ledit_buffer_get_line(buffer, line); if (l->text_dirty) err_text_dirty(buffer, line); /* FIXME: configure color */ @@ -192,32 +221,36 @@ ledit_set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end attr0->end_index = end_byte; attr1->start_index = start_byte; attr1->end_index = end_byte; - 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); + #if PANGO_VERSION_CHECK(1, 44, 0) + PangoAttribute *attr2 = pango_attr_insert_hyphens_new(FALSE); pango_attr_list_insert(list, attr2); + #endif pango_layout_set_attributes(l->layout, list); l->dirty = 1; } void -ledit_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index) { - ledit_line *l = ledit_get_line(buffer, line); +ledit_buffer_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index) { + ledit_line *l = ledit_buffer_get_line(buffer, line); if (l->text_dirty) err_text_dirty(buffer, line); - if (buffer->state->mode == NORMAL) { + if (buffer->common->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); + #if PANGO_VERSION_CHECK(1, 44, 0) + PangoAttribute *attr2 = pango_attr_insert_hyphens_new(FALSE); pango_attr_list_insert(list, attr2); + #endif pango_layout_set_attributes(l->layout, list); } else { pango_layout_set_attributes(l->layout, basic_attrs); @@ -226,43 +259,46 @@ ledit_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index) { } void -ledit_wipe_line_cursor_attrs(ledit_buffer *buffer, int line) { - ledit_line *l = ledit_get_line(buffer, line); +ledit_buffer_wipe_line_cursor_attrs(ledit_buffer *buffer, int line) { + ledit_line *l = ledit_buffer_get_line(buffer, line); pango_layout_set_attributes(l->layout, basic_attrs); l->dirty = 1; } -/* FIXME: To simplify this a bit, maybe just copy text to lbuf first and +/* FIXME: To simplify this a bit, maybe just copy text to txtbuf first and then insert it in one go instead of having this complex logic */ void -ledit_insert_text_from_line( +ledit_buffer_insert_text_from_line( ledit_buffer *buffer, int dst_line, int dst_index, int src_line, int src_index, int src_len, - lbuf *text_ret) { - ledit_insert_text_from_line_base( + txtbuf *text_ret) { + ledit_buffer_insert_text_from_line_base( buffer, dst_line, dst_index, src_line, src_index, src_len, text_ret ); - ledit_recalc_line(buffer, dst_line); + ledit_buffer_recalc_line(buffer, dst_line); } +/* FIXME: check if there can be bugs when a newline is inserted in some way other + other than pasting or pressing enter */ + void -ledit_insert_text_from_line_base( +ledit_buffer_insert_text_from_line_base( ledit_buffer *buffer, int dst_line, int dst_index, int src_line, int src_index, int src_len, - lbuf *text_ret) { + txtbuf *text_ret) { assert(dst_line != src_line); - ledit_line *ll = ledit_get_line(buffer, src_line); + ledit_line *ll = ledit_buffer_get_line(buffer, src_line); if (src_len == -1) src_len = ll->len - src_index; if (text_ret != NULL) { - lbuf_grow(text_ret, src_len); + txtbuf_grow(text_ret, src_len); text_ret->len = src_len; } if (src_index >= ll->gap) { /* all text to insert is after gap */ - ledit_insert_text_base( + ledit_buffer_insert_text_base( buffer, dst_line, dst_index, ll->text + src_index + ll->cap - ll->len, src_len ); @@ -275,7 +311,7 @@ ledit_insert_text_from_line_base( } } else if (ll->gap - src_index >= src_len) { /* all text to insert is before gap */ - ledit_insert_text_base( + ledit_buffer_insert_text_base( buffer, dst_line, dst_index, ll->text + src_index, src_len ); @@ -288,12 +324,12 @@ ledit_insert_text_from_line_base( } } else { /* insert part of text before gap */ - ledit_insert_text_base( + ledit_buffer_insert_text_base( buffer, dst_line, dst_index, ll->text + src_index, ll->gap - src_index ); /* insert part of text after gap */ - ledit_insert_text_base( + ledit_buffer_insert_text_base( buffer, dst_line, dst_index + ll->gap - src_index, ll->text + ll->gap + ll->cap - ll->len, src_len - ll->gap + src_index @@ -386,13 +422,13 @@ resize_and_move_text_gap(ledit_line *line, int min_size, int index) { } void -ledit_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, int len) { - ledit_insert_text_base(buffer, line_index, index, text, len); - ledit_recalc_line(buffer, line_index); +ledit_buffer_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, int len) { + ledit_buffer_insert_text_base(buffer, line_index, index, text, len); + ledit_buffer_recalc_line(buffer, line_index); } void -ledit_insert_text_base(ledit_buffer *buffer, int line_index, int index, char *text, int len) { +ledit_buffer_insert_text_base(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); @@ -415,7 +451,7 @@ ledit_insert_text_base(ledit_buffer *buffer, int line_index, int index, char *te avoid another copy before rendering */ /* void -ledit_insert_text_final(ledit_buffer *buffer, int line_index, int index, char *text, int len) { +ledit_buffer_insert_text_final(ledit_buffer *buffer, int line_index, int index, char *text, int len) { } */ @@ -438,27 +474,27 @@ strchr_len(char *text, char c, long len) { Maybe len isn't needed anyways? It might be possible to enforce that text just always has to be null-terminated. */ void -ledit_insert_text_with_newlines( +ledit_buffer_insert_text_with_newlines( ledit_buffer *buffer, int line_index, int index, char *text, long len, int *end_line_ret, int *end_char_ret) { int end; - ledit_insert_text_with_newlines_base( + ledit_buffer_insert_text_with_newlines_base( buffer, line_index, index, text, len, &end, end_char_ret ); if (end_line_ret) *end_line_ret = end; if (line_index == end) - ledit_recalc_line(buffer, line_index); + ledit_buffer_recalc_line(buffer, line_index); else - ledit_recalc_from_line(buffer, line_index); + ledit_buffer_recalc_from_line(buffer, line_index); } /* FIXME: Check for integer overflow when casting to int */ void -ledit_insert_text_with_newlines_base( +ledit_buffer_insert_text_with_newlines_base( ledit_buffer *buffer, int line_index, int index, char *text, long len, @@ -469,69 +505,86 @@ ledit_insert_text_with_newlines_base( char *cur, *last = text; int cur_line = line_index; int cur_index = index; + /* FIXME: strchr_len isn't really needed when the lines are normalized anyways */ while ((cur = strchr_len(last, '\n', rem_len)) != NULL) { /* FIXME: inefficient because there's no gap buffer yet */ - ledit_append_line_base(buffer, cur_line, cur_index); - ledit_insert_text_base(buffer, cur_line, cur_index, last, cur - last); + ledit_buffer_append_line_base(buffer, cur_line, cur_index); + ledit_buffer_insert_text_base(buffer, cur_line, cur_index, last, cur - last); cur_index = 0; cur_line++; rem_len -= cur - last + 1; last = cur + 1; } /* FIXME: check how legal this casting between pointers and ints is */ - ledit_insert_text_base(buffer, cur_line, cur_index, last, text + len - last); + ledit_buffer_insert_text_base(buffer, cur_line, cur_index, last, text + len - last); if (end_line_ret) *end_line_ret = cur_line; if (end_char_ret) *end_char_ret = cur_index + text + len - last; } +/* FIXME: is this even needed? */ +static int +line_visible_callback(void *data, int line) { + return ledit_buffer_line_visible((ledit_buffer*)data, line); +} + /* FIXME: standardize variable names (line/line_index, etc.) */ void -ledit_render_line(ledit_buffer *buffer, int line_index) { +ledit_buffer_render_line(ledit_buffer *buffer, int line_index) { /* FIXME: check for <= 0 on size */ - ledit_line *line = &buffer->lines[line_index]; + ledit_line *line = ledit_buffer_get_line(buffer, line_index); /* this shouldn't happen if the functions here are used correctly */ if (line->text_dirty || line->h_dirty) err_text_dirty(buffer, 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); + if (line->cache_index == -1) { + int new_index = ledit_get_unneeded_cache_index(buffer->cache, buffer, &line_visible_callback); + ledit_cache_pixmap *tmp_pix = ledit_get_cache_pixmap(buffer->cache, new_index); + if (tmp_pix->line >= 0) { + ledit_line *old_line = ledit_buffer_get_line(buffer, tmp_pix->line); + old_line->cache_index = -1; + } + tmp_pix->line = line_index; + line->cache_index = new_index; + } + ledit_cache_pixmap *pix = ledit_get_cache_pixmap(buffer->cache, 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 + buffer->common->dpy, buffer->window->drawable, + line->w + 10, line->h + 10, buffer->common->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 + buffer->common->dpy, pix->pixmap, + buffer->common->vis, buffer->common->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); + XFreePixmap(buffer->common->dpy, pix->pixmap); pix->pixmap = XCreatePixmap( - buffer->state->dpy, buffer->state->drawable, - new_w, new_h, buffer->state->depth + buffer->common->dpy, buffer->window->drawable, + new_w, new_h, buffer->common->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); + XftDrawRect(pix->draw, &buffer->theme->text_bg, 0, 0, line->w, line->h); + pango_xft_render_layout(pix->draw, &buffer->theme->text_fg, line->layout, 0, 0); line->dirty = 0; } static void init_line(ledit_buffer *buffer, ledit_line *line) { + int text_w, text_h; line->parent_buffer = buffer; - line->layout = pango_layout_new(buffer->state->context); - pango_layout_set_width(line->layout, (buffer->state->text_w) * PANGO_SCALE); - pango_layout_set_font_description(line->layout, buffer->state->font); + line->layout = pango_layout_new(buffer->window->context); + ledit_window_get_textview_size(buffer->window, &text_w, &text_h); + pango_layout_set_width(line->layout, text_w * PANGO_SCALE); + pango_layout_set_font_description(line->layout, buffer->window->font); pango_layout_set_wrap(line->layout, PANGO_WRAP_WORD_CHAR); pango_layout_set_attributes(line->layout, basic_attrs); line->gap = 0; @@ -547,19 +600,19 @@ init_line(ledit_buffer *buffer, ledit_line *line) { line->w = line->h = 0; /* FIXME: does this set line height reasonably when no text yet? */ pango_layout_get_pixel_size(line->layout, &line->w, &line->h); - line->w = buffer->state->text_w; + line->w = text_w; line->y_offset = 0; } void -ledit_append_line(ledit_buffer *buffer, int line_index, int text_index) { - ledit_append_line_base(buffer, line_index, text_index); - ledit_recalc_from_line(buffer, line_index); +ledit_buffer_append_line(ledit_buffer *buffer, int line_index, int text_index) { + ledit_buffer_append_line_base(buffer, line_index, text_index); + ledit_buffer_recalc_from_line(buffer, line_index); } /* FIXME: error checking (index out of bounds, etc.) */ void -ledit_append_line_base(ledit_buffer *buffer, int line_index, int text_index) { +ledit_buffer_append_line_base(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) @@ -573,12 +626,12 @@ ledit_append_line_base(ledit_buffer *buffer, int line_index, int text_index) { buffer->lines + line_index + 1, (buffer->lines_num - (line_index + 1)) * sizeof(ledit_line) ); - ledit_line *new_l = ledit_get_line(buffer, line_index + 1); + ledit_line *new_l = ledit_buffer_get_line(buffer, line_index + 1); init_line(buffer, new_l); buffer->lines_num++; if (text_index != -1) { - ledit_line *l = ledit_get_line(buffer, line_index); - ledit_insert_text_from_line_base( + ledit_line *l = ledit_buffer_get_line(buffer, line_index); + ledit_buffer_insert_text_from_line_base( buffer, line_index + 1, 0, line_index, text_index, -1, NULL ); @@ -591,19 +644,19 @@ ledit_append_line_base(ledit_buffer *buffer, int line_index, int text_index) { /* this is very weird and ugly with the recalc */ void -ledit_delete_line_entries(ledit_buffer *buffer, int index1, int index2) { - ledit_delete_line_entries_base(buffer, index1, index2); - ledit_recalc_from_line(buffer, index1 > 0 ? index1 - 1 : 0); +ledit_buffer_delete_line_entries(ledit_buffer *buffer, int index1, int index2) { + ledit_buffer_delete_line_entries_base(buffer, index1, index2); + ledit_buffer_recalc_from_line(buffer, index1 > 0 ? index1 - 1 : 0); } -/* IMPORTANT: ledit_recalc_from_line needs to be called sometime after this! */ +/* IMPORTANT: ledit_buffer_recalc_from_line needs to be called sometime after this! */ void -ledit_delete_line_entries_base(ledit_buffer *buffer, int index1, int index2) { +ledit_buffer_delete_line_entries_base(ledit_buffer *buffer, int index1, int index2) { ledit_line *l; /* FIXME: make sure this is always true */ assert(index2 - index1 != buffer->lines_num); for (int i = index1; i <= index2; i++) { - l = ledit_get_line(buffer, i); + l = ledit_buffer_get_line(buffer, i); g_object_unref(l->layout); free(l->text); } @@ -617,29 +670,29 @@ ledit_delete_line_entries_base(ledit_buffer *buffer, int index1, int index2) { buffer->lines_num -= index2 - index1 + 1; /* force a recalc of the lines */ if (index1 == 0) { - l = ledit_get_line(buffer, 0); + l = ledit_buffer_get_line(buffer, 0); l->y_offset = 0; l->h_dirty = 1; } else { - l = ledit_get_line(buffer, index1 - 1); + l = ledit_buffer_get_line(buffer, index1 - 1); l->h_dirty = 1; } } void -ledit_delete_line_entry(ledit_buffer *buffer, int index) { - ledit_delete_line_entries(buffer, index, index); +ledit_buffer_delete_line_entry(ledit_buffer *buffer, int index) { + ledit_buffer_delete_line_entries(buffer, index, index); } void -ledit_delete_line_entry_base(ledit_buffer *buffer, int index) { - ledit_delete_line_entries_base(buffer, index, index); +ledit_buffer_delete_line_entry_base(ledit_buffer *buffer, int index) { + ledit_buffer_delete_line_entries_base(buffer, index, index); } /* 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) { +ledit_buffer_get_line(ledit_buffer *buffer, int index) { return &buffer->lines[index]; } @@ -647,11 +700,11 @@ ledit_get_line(ledit_buffer *buffer, int index) { * - if height hasn't changed, nothing further is done * - if height has changed, offset of all following lines is changed */ void -ledit_recalc_line(ledit_buffer *buffer, int line) { +ledit_buffer_recalc_line(ledit_buffer *buffer, int line) { int w, h; - ledit_line *l = ledit_get_line(buffer, line); + ledit_line *l = ledit_buffer_get_line(buffer, line); if (l->text_dirty) { - ledit_normalize_line(l); + ledit_buffer_normalize_line(l); pango_layout_set_text(l->layout, l->text, l->len); l->text_dirty = 0; } @@ -663,7 +716,7 @@ ledit_recalc_line(ledit_buffer *buffer, int line) { long off = l->y_offset + h; l->h = h; for (int i = line + 1; i < buffer->lines_num; i++) { - l = ledit_get_line(buffer, i); + l = ledit_buffer_get_line(buffer, i); l->y_offset = off; off += l->h; } @@ -674,14 +727,14 @@ ledit_recalc_line(ledit_buffer *buffer, int line) { /* set text of pango layout and recalculate height * and offset for all lines starting at 'line' */ void -ledit_recalc_from_line(ledit_buffer *buffer, int line) { +ledit_buffer_recalc_from_line(ledit_buffer *buffer, int line) { int w, h; - ledit_line *l = ledit_get_line(buffer, line); + ledit_line *l = ledit_buffer_get_line(buffer, line); long off = l->y_offset; for (int i = line; i < buffer->lines_num; i++) { - l = ledit_get_line(buffer, i); + l = ledit_buffer_get_line(buffer, i); if (l->text_dirty) { - ledit_normalize_line(l); + ledit_buffer_normalize_line(l); pango_layout_set_text(l->layout, l->text, l->len); l->text_dirty = 0; pango_layout_get_pixel_size(l->layout, &w, &h); @@ -697,19 +750,21 @@ ledit_recalc_from_line(ledit_buffer *buffer, int line) { buffer->total_height = off; } -/* short for 'ledit_recalc_from_line' starting at line 0 */ +/* short for 'ledit_buffer_recalc_from_line' starting at line 0 */ void -ledit_recalc_all_lines(ledit_buffer *buffer) { +ledit_buffer_recalc_all_lines(ledit_buffer *buffer) { /* force first line to offset 0, just in case */ - ledit_line *l = ledit_get_line(buffer, 0); + ledit_line *l = ledit_buffer_get_line(buffer, 0); l->y_offset = 0; - ledit_recalc_from_line(buffer, 0); + ledit_buffer_recalc_from_line(buffer, 0); } int -ledit_line_visible(ledit_buffer *buffer, int index) { - ledit_line *l = ledit_get_line(buffer, index); - return l->y_offset < buffer->display_offset + buffer->state->text_h && +ledit_buffer_line_visible(ledit_buffer *buffer, int index) { + int text_w, text_h; + ledit_window_get_textview_size(buffer->window, &text_w, &text_h); + ledit_line *l = ledit_buffer_get_line(buffer, index); + return l->y_offset < buffer->display_offset + text_h && l->y_offset + l->h > buffer->display_offset; } @@ -718,17 +773,17 @@ ledit_line_visible(ledit_buffer *buffer, int index) { * - if the last range ends at the end of a line, the newline is *not* included * - the range must be sorted already */ size_t -ledit_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2) { +ledit_buffer_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2) { assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); size_t len = 0; - ledit_line *ll = ledit_get_line(buffer, line1); + ledit_line *ll = ledit_buffer_get_line(buffer, line1); if (line1 == line2) { len = byte2 - byte1; } else { /* + 1 for newline */ len = ll->len - byte1 + byte2 + 1; for (int i = line1 + 1; i < line2; i++) { - ll = ledit_get_line(buffer, i); + ll = ledit_buffer_get_line(buffer, i); len += ll->len + 1; } } @@ -746,11 +801,11 @@ ledit_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2) * - dst must be large enough to contain the text and NUL * - the range must be sorted already */ void -ledit_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2) { +ledit_buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2) { assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); - ledit_line *ll1 = ledit_get_line(buffer, line1); - ledit_line *ll2 = ledit_get_line(buffer, line2); - ledit_normalize_line(ll1); + ledit_line *ll1 = ledit_buffer_get_line(buffer, line1); + ledit_line *ll2 = ledit_buffer_get_line(buffer, line2); + ledit_buffer_normalize_line(ll1); if (line1 == line2) { memcpy(dst, ll1->text + byte1, byte2 - byte1); dst[byte2 - byte1] = '\0'; @@ -761,14 +816,14 @@ ledit_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2 dst[cur_pos] = '\n'; cur_pos++; for (int i = line1 + 1; i < line2; i++) { - ledit_line *ll = ledit_get_line(buffer, i); - ledit_normalize_line(ll); + ledit_line *ll = ledit_buffer_get_line(buffer, i); + ledit_buffer_normalize_line(ll); memcpy(dst + cur_pos, ll->text, ll->len); cur_pos += ll->len; dst[cur_pos] = '\n'; cur_pos++; } - ledit_normalize_line(ll2); + ledit_buffer_normalize_line(ll2); memcpy(dst + cur_pos, ll2->text, byte2); cur_pos += byte2; dst[cur_pos] = '\0'; @@ -781,15 +836,15 @@ ledit_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2 * - the range must be sorted already * - returns the length of the text, not including the NUL */ void -ledit_copy_text_to_lbuf( +ledit_buffer_copy_text_to_txtbuf( ledit_buffer *buffer, - lbuf *buf, + txtbuf *buf, int line1, int byte1, int line2, int byte2) { assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); - size_t len = ledit_textlen(buffer, line1, byte1, line2, byte2); - lbuf_grow(buf, len + 1); - ledit_copy_text(buffer, buf->text, line1, byte1, line2, byte2); + size_t len = ledit_buffer_textlen(buffer, line1, byte1, line2, byte2); + txtbuf_grow(buf, len + 1); + ledit_buffer_copy_text(buffer, buf->text, line1, byte1, line2, byte2); buf->len = len; } @@ -797,16 +852,17 @@ ledit_copy_text_to_lbuf( #define LINE_CHAR(line, i) ((i) < (line)->gap ? (line)->text[i] : (line)->text[i + (line)->cap - (line)->len]) int -ledit_prev_utf8(ledit_line *line, int index) { +ledit_line_prev_utf8(ledit_line *line, int index) { int i = index - 1; /* find valid utf8 char - this probably needs to be improved */ + /* FIXME: don't go off end or beginning */ while (i > 0 && ((LINE_CHAR(line, i) & 0xC0) == 0x80)) i--; return i; } int -ledit_next_utf8(ledit_line *line, int index) { +ledit_line_next_utf8(ledit_line *line, int index) { int i = index + 1; while (i < line->len && ((LINE_CHAR(line, i) & 0xC0) == 0x80)) i++; @@ -818,13 +874,13 @@ ledit_next_utf8(ledit_line *line, int index) { static void delete_line_section(ledit_buffer *buffer, int line, int start, int length) { delete_line_section_base(buffer, line, start, length); - ledit_recalc_line(buffer, line); + ledit_buffer_recalc_line(buffer, line); } */ static void delete_line_section_base(ledit_buffer *buffer, int line, int start, int length) { - ledit_line *l = ledit_get_line(buffer, line); + ledit_line *l = ledit_buffer_get_line(buffer, line); if (start <= l->gap && start + length >= l->gap) { l->gap = start; } else if (start < l->gap && start + length < l->gap) { @@ -848,22 +904,22 @@ delete_line_section_base(ledit_buffer *buffer, int line, int start, int length) } int -ledit_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index, int dir) { - int new_index = ledit_delete_unicode_char_base(buffer, line_index, byte_index, dir); - ledit_recalc_line(buffer, line_index); +ledit_buffer_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index, int dir) { + int new_index = ledit_buffer_delete_unicode_char_base(buffer, line_index, byte_index, dir); + ledit_buffer_recalc_line(buffer, line_index); return new_index; } int -ledit_delete_unicode_char_base(ledit_buffer *buffer, int line_index, int byte_index, int dir) { - ledit_line *l = ledit_get_line(buffer, line_index); +ledit_buffer_delete_unicode_char_base(ledit_buffer *buffer, int line_index, int byte_index, int dir) { + ledit_line *l = ledit_buffer_get_line(buffer, line_index); int new_index = byte_index; if (dir < 0) { - int i = ledit_prev_utf8(l, byte_index); + int i = ledit_line_prev_utf8(l, byte_index); delete_line_section_base(buffer, line_index, i, byte_index - i); new_index = i; } else { - int i = ledit_next_utf8(l, byte_index); + int i = ledit_line_next_utf8(l, byte_index); delete_line_section_base(buffer, line_index, byte_index, i - byte_index); } return new_index; @@ -872,7 +928,7 @@ ledit_delete_unicode_char_base(ledit_buffer *buffer, int line_index, int byte_in static void normalize_and_set_pango_text(ledit_line *line) { if (line->text_dirty) { - ledit_normalize_line(line); + ledit_buffer_normalize_line(line); pango_layout_set_text(line->layout, line->text, line->len); line->text_dirty = 0; line->h_dirty = 1; @@ -894,7 +950,7 @@ ledit_pos_to_x_softline(ledit_line *line, int pos, int *x_ret, int *softline_ret /* if in normal mode, change position to the middle of the current rectangle so that moving around won't jump weirdly */ /* FIXME: also in visual? */ - if (line->parent_buffer->state->mode == NORMAL) { + if (line->parent_buffer->common->mode == NORMAL) { PangoRectangle rect; pango_layout_index_to_pos(line->layout, pos, &rect); *x_ret += rect.width / 2; @@ -919,43 +975,43 @@ ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret) { pango_line, x_relative, pos_ret, &trailing ); /* if in insert mode, snap to the nearest border between graphemes */ - if (line->parent_buffer->state->mode == INSERT) { + if (line->parent_buffer->common->mode == INSERT) { while (trailing > 0) { trailing--; - *pos_ret = ledit_next_utf8(line, *pos_ret); + *pos_ret = ledit_line_next_utf8(line, *pos_ret); } } } /* FIXME: make sure PangoLayout has newest text already when these functions are called */ int -ledit_get_legal_normal_pos(ledit_buffer *buffer, int line, int pos) { +ledit_buffer_get_legal_normal_pos(ledit_buffer *buffer, int line, int pos) { /* move back one grapheme if at end of line */ int ret = pos; - ledit_line *final_line = ledit_get_line(buffer, line); + ledit_line *final_line = ledit_buffer_get_line(buffer, line); normalize_and_set_pango_text(final_line); if (pos == final_line->len && pos > 0) { int nattrs; const PangoLogAttr *attrs = pango_layout_get_log_attrs_readonly(final_line->layout, &nattrs); int cur = nattrs - 2; - ret = ledit_prev_utf8(final_line, ret); + ret = ledit_line_prev_utf8(final_line, ret); while (ret > 0 && cur > 0 && !attrs[cur].is_cursor_position) { cur--; - ret = ledit_prev_utf8(final_line, ret); + ret = ledit_line_prev_utf8(final_line, ret); } } return ret; } void -ledit_delete_range( +ledit_buffer_delete_range( ledit_buffer *buffer, int line_based, int line_index1, int byte_index1, int line_index2, int byte_index2, int *new_line_ret, int *new_byte_ret, - ledit_range *final_range_ret, lbuf *text_ret) { - ledit_delete_range_base( + ledit_range *final_range_ret, txtbuf *text_ret) { + ledit_buffer_delete_range_base( buffer, line_based, line_index1, byte_index1, line_index2, byte_index2, @@ -965,24 +1021,24 @@ ledit_delete_range( /* need to start recalculating one line before in case first line was deleted and offset is now wrong */ int min = line_index1 < line_index2 ? line_index1 : line_index2; - ledit_recalc_from_line(buffer, min > 0 ? min - 1 : min); + ledit_buffer_recalc_from_line(buffer, min > 0 ? min - 1 : min); } /* FIXME: use at least somewhat sensible variable names */ void -ledit_delete_range_base( +ledit_buffer_delete_range_base( ledit_buffer *buffer, int line_based, int line_index1, int byte_index1, int line_index2, int byte_index2, int *new_line_ret, int *new_byte_ret, - ledit_range *final_range_ret, lbuf *text_ret) { + ledit_range *final_range_ret, txtbuf *text_ret) { /* FIXME: Oh boy, this is nasty */ /* range line x, range byte x */ int rgl1 = 0, rgb1 = 0, rgl2 = 0, rgb2 = 0; int new_line = 0, new_byte = 0; if (line_based) { int x, softline1, softline2; - ledit_line *line1 = ledit_get_line(buffer, line_index1); + ledit_line *line1 = ledit_buffer_get_line(buffer, line_index1); normalize_and_set_pango_text(line1); ledit_pos_to_x_softline(line1, byte_index1, &x, &softline1); if (line_index1 == line_index2) { @@ -999,7 +1055,7 @@ ledit_delete_range_base( /* cursor can be moved to next hard line */ new_line = line_index1; ledit_x_softline_to_pos( - ledit_get_line(buffer, line_index1 + 1), + ledit_buffer_get_line(buffer, line_index1 + 1), x, 0, &new_byte ); rgl1 = line_index1; @@ -1012,7 +1068,7 @@ ledit_delete_range_base( /* note: logically, line_index1 - 1 must be >= 0 because buffer->lines_num > 1 && line_index1 >= buffer->lines_num - 1 */ new_line = line_index1 - 1; - ledit_line *prevline = ledit_get_line(buffer, line_index1 - 1); + ledit_line *prevline = ledit_buffer_get_line(buffer, line_index1 - 1); softlines = pango_layout_get_line_count(prevline->layout); ledit_x_softline_to_pos(prevline, x, softlines - 1, &new_byte); rgl1 = line_index1 - 1; @@ -1021,20 +1077,20 @@ ledit_delete_range_base( rgb2 = line1->len; } if (text_ret) { - ledit_copy_text_to_lbuf( + ledit_buffer_copy_text_to_txtbuf( buffer, text_ret, rgl1, rgb1, rgl2, rgb2 ); } - ledit_delete_line_entry_base(buffer, line_index1); + ledit_buffer_delete_line_entry_base(buffer, line_index1); } else { assert(pl2->start_index + pl2->length - pl1->start_index >= 0); rgl1 = rgl2 = line_index1; rgb1 = pl1->start_index; rgb2 = pl2->start_index + pl2->length; if (text_ret) { - ledit_copy_text_to_lbuf( + ledit_buffer_copy_text_to_txtbuf( buffer, text_ret, rgl1, rgb1, rgl2, rgb2 @@ -1046,19 +1102,19 @@ ledit_delete_range_base( if (l2 == softlines - 1 && line_index1 < buffer->lines_num - 1) { new_line = line_index1 + 1; ledit_x_softline_to_pos( - ledit_get_line(buffer, line_index1 + 1), + ledit_buffer_get_line(buffer, line_index1 + 1), x, 0, &new_byte ); } else if (l2 < softlines - 1) { new_line = line_index1; ledit_x_softline_to_pos( - ledit_get_line(buffer, line_index1), + ledit_buffer_get_line(buffer, line_index1), x, l1, &new_byte ); } else if (l1 > 0) { new_line = line_index1; ledit_x_softline_to_pos( - ledit_get_line(buffer, line_index1), + ledit_buffer_get_line(buffer, line_index1), x, l1 - 1, &new_byte ); } else { @@ -1081,8 +1137,8 @@ ledit_delete_range_base( l2 = line_index1; b2 = byte_index1; } - ledit_line *ll1 = ledit_get_line(buffer, l1); - ledit_line *ll2 = ledit_get_line(buffer, l2); + ledit_line *ll1 = ledit_buffer_get_line(buffer, l1); + ledit_line *ll2 = ledit_buffer_get_line(buffer, l2); normalize_and_set_pango_text(ll1); normalize_and_set_pango_text(ll2); pango_layout_index_to_line_x(ll1->layout, b1, 0, &sl1, &x_useless); @@ -1097,20 +1153,20 @@ ledit_delete_range_base( rgb1 = 0; rgb2 = ll2->len; if (text_ret) { - ledit_copy_text_to_lbuf( + ledit_buffer_copy_text_to_txtbuf( buffer, text_ret, rgl1, rgb1, rgl2, rgb2 ); } delete_line_section_base(buffer, l1, 0, ll1->len); - ledit_delete_line_entries_base(buffer, l1 + 1, l2); + ledit_buffer_delete_line_entries_base(buffer, l1 + 1, l2); new_line = 0; new_byte = 0; } else { if (l2 == buffer->lines_num - 1) { new_line = l1 - 1; - ledit_line *new_lline = ledit_get_line(buffer, new_line); + ledit_line *new_lline = ledit_buffer_get_line(buffer, new_line); int new_softlines = pango_layout_get_line_count(new_lline->layout); ledit_x_softline_to_pos(new_lline, x, new_softlines - 1, &new_byte); rgl1 = l1 - 1; @@ -1119,7 +1175,7 @@ ledit_delete_range_base( rgb2 = ll2->len; } else { new_line = l1; - ledit_line *nextline = ledit_get_line(buffer, l2 + 1); + ledit_line *nextline = ledit_buffer_get_line(buffer, l2 + 1); ledit_x_softline_to_pos( nextline, x, 0, &new_byte ); @@ -1129,13 +1185,13 @@ ledit_delete_range_base( rgb2 = 0; } if (text_ret) { - ledit_copy_text_to_lbuf( + ledit_buffer_copy_text_to_txtbuf( buffer, text_ret, rgl1, rgb1, rgl2, rgb2 ); } - ledit_delete_line_entries_base(buffer, l1, l2); + ledit_buffer_delete_line_entries_base(buffer, l1, l2); } } else if (sl1 == 0) { rgl1 = l1; @@ -1143,7 +1199,7 @@ ledit_delete_range_base( rgl2 = l2; rgb2 = pl2->start_index + pl2->length; if (text_ret) { - ledit_copy_text_to_lbuf( + ledit_buffer_copy_text_to_txtbuf( buffer, text_ret, rgl1, rgb1, rgl2, rgb2 @@ -1152,7 +1208,7 @@ ledit_delete_range_base( delete_line_section_base(buffer, l2, 0, pl2->start_index + pl2->length); new_line = l1; ledit_x_softline_to_pos(ll2, x, 0, &new_byte); - ledit_delete_line_entries_base(buffer, l1, l2 - 1); + ledit_buffer_delete_line_entries_base(buffer, l1, l2 - 1); } else if (sl2 == softlines - 1) { rgl1 = l1; rgb1 = pl1->start_index; @@ -1164,19 +1220,19 @@ ledit_delete_range_base( } else { new_line = l1 + 1; ledit_x_softline_to_pos( - ledit_get_line(buffer, l2 + 1), + ledit_buffer_get_line(buffer, l2 + 1), x, 0, &new_byte ); } if (text_ret) { - ledit_copy_text_to_lbuf( + ledit_buffer_copy_text_to_txtbuf( buffer, text_ret, rgl1, rgb1, rgl2, rgb2 ); } delete_line_section_base(buffer, l1, pl1->start_index, ll1->len - pl1->start_index); - ledit_delete_line_entries_base(buffer, l1 + 1, l2); + ledit_buffer_delete_line_entries_base(buffer, l1 + 1, l2); } else { /* FIXME: this could be made nicer by just using the range to delete all in one go at the end */ @@ -1185,20 +1241,20 @@ ledit_delete_range_base( rgl2 = l2; rgb2 = pl2->start_index + pl2->length; if (text_ret) { - ledit_copy_text_to_lbuf( + ledit_buffer_copy_text_to_txtbuf( buffer, text_ret, rgl1, rgb1, rgl2, rgb2 ); } delete_line_section_base(buffer, l1, pl1->start_index, ll1->len - pl1->start_index); - ledit_insert_text_from_line_base( + ledit_buffer_insert_text_from_line_base( buffer, l1, pl1->start_index, l2, pl2->start_index + pl2->length, ll2->len - (pl2->start_index + pl2->length), NULL ); - ledit_delete_line_entries_base(buffer, l1 + 1, l2); + ledit_buffer_delete_line_entries_base(buffer, l1 + 1, l2); new_line = l1; int new_softlines = pango_layout_get_line_count(ll1->layout); /* it's technically possible that the remaining part of the @@ -1223,7 +1279,7 @@ ledit_delete_range_base( rgb2 = byte_index1; } if (text_ret) { - ledit_copy_text_to_lbuf( + ledit_buffer_copy_text_to_txtbuf( buffer, text_ret, rgl1, rgb1, rgl2, rgb2 @@ -1245,24 +1301,24 @@ ledit_delete_range_base( rgb2 = byte_index1; } if (text_ret) { - ledit_copy_text_to_lbuf( + ledit_buffer_copy_text_to_txtbuf( buffer, text_ret, rgl1, rgb1, rgl2, rgb2 ); } - ledit_line *line1 = ledit_get_line(buffer, rgl1); - ledit_line *line2 = ledit_get_line(buffer, rgl2); + ledit_line *line1 = ledit_buffer_get_line(buffer, rgl1); + ledit_line *line2 = ledit_buffer_get_line(buffer, rgl2); delete_line_section_base(buffer, rgl1, rgb1, line1->len - rgb1); - ledit_insert_text_from_line_base( + ledit_buffer_insert_text_from_line_base( buffer, rgl1, rgb1, rgl2, rgb2, line2->len - rgb2, NULL ); new_line = rgl1; new_byte = rgb1; - ledit_delete_line_entries_base(buffer, rgl1 + 1, rgl2); + ledit_buffer_delete_line_entries_base(buffer, rgl1 + 1, rgl2); } - if (buffer->state->mode == NORMAL) - new_byte = ledit_get_legal_normal_pos(buffer, new_line, new_byte); + if (buffer->common->mode == NORMAL) + new_byte = ledit_buffer_get_legal_normal_pos(buffer, new_line, new_byte); } if (final_range_ret) { final_range_ret->line1 = rgl1; @@ -1275,3 +1331,340 @@ ledit_delete_range_base( if (new_byte_ret) *new_byte_ret = new_byte; } + +/* FIXME */ +void +ledit_buffer_resize_textview(ledit_buffer *buffer) { + buffer->total_height = 0; + int tmp_w, tmp_h; + int text_w, text_h; + ledit_window_get_textview_size(buffer->window, &text_w, &text_h); + for (int i = 0; i < buffer->lines_num; i++) { + ledit_line *line = ledit_buffer_get_line(buffer, i); + pango_layout_set_width(line->layout, text_w * PANGO_SCALE); + pango_layout_get_pixel_size(line->layout, &tmp_w, &tmp_h); + line->h = tmp_h; + line->w = text_w; + line->y_offset = buffer->total_height; + line->dirty = 1; + buffer->total_height += tmp_h; + } + ledit_window_set_scroll_max(buffer->window, buffer->total_height); + if (buffer->display_offset > 0 && + buffer->display_offset + text_h >= buffer->total_height) { + buffer->display_offset = buffer->total_height - text_h; + if (buffer->display_offset < 0) + buffer->display_offset = 0; + ledit_window_set_scroll_pos(buffer->window, buffer->display_offset); + } +} + +void +ledit_xy_to_line_byte(ledit_buffer *buffer, int x, int y, int *line_ret, int *byte_ret) { + /* FIXME: store current line offset to speed this up */ + /* FIXME: use y_offset in lines */ + long h = 0; + double pos = buffer->display_offset + y; + for (int i = 0; i < buffer->lines_num; i++) { + ledit_line *line = ledit_buffer_get_line(buffer, i); + if ((h <= pos && h + line->h > pos) || i == buffer->lines_num - 1) { + int index, trailing; + pango_layout_xy_to_index( + line->layout, + x * PANGO_SCALE, (int)(pos - h) * PANGO_SCALE, + &index, &trailing + ); + while (trailing > 0) { + trailing--; + index = ledit_line_next_utf8(line, index); + } + *line_ret = i; + *byte_ret = index; + break; + } + h += line->h; + } +} + +void +ledit_buffer_ensure_cursor_shown(ledit_buffer *buffer) { + PangoRectangle strong, weak; + int text_w, text_h; + ledit_window_get_textview_size(buffer->window, &text_w, &text_h); + ledit_line *line = ledit_buffer_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 + text_h) { + buffer->display_offset = + cursor_y - text_h + strong.height / PANGO_SCALE; + } + ledit_window_set_scroll_pos(buffer->window, buffer->display_offset); +} + +static void +swap(int *a, int *b) { + int tmp = *a; + *a = *b; + *b = tmp; +} + +static void +sort_selection(int *line1, int *byte1, int *line2, int *byte2) { + if (*line1 > *line2) { + swap(line1, line2); + swap(byte1, byte2); + } else if (*line1 == *line2 && *byte1 > *byte2) { + swap(byte1, byte2); + } +} + +/* FIXME: don't reset selection when selection is clicked away */ +/* FIXME: when selecting with mouse, only call this when button is released */ +/* lines and bytes need to be sorted already! */ +static void +copy_selection_to_x_primary(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2) { + /* FIXME: let window handle this */ + txtbuf *primary = ledit_window_get_primary_clipboard_buffer(); + ledit_buffer_copy_text_to_txtbuf(buffer, primary, line1, byte1, line2, byte2); + XSetSelectionOwner(buffer->common->dpy, XA_PRIMARY, buffer->window->xwin, CurrentTime); + /* + FIXME + if (XGetSelectionOwner(state.dpy, XA_PRIMARY) != state.win) + selclear(); + */ +} + +void +ledit_buffer_set_selection(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2) { + if (line1 == buffer->sel.line1 && line2 == buffer->sel.line2 && + byte1 == buffer->sel.byte1 && byte2 == buffer->sel.byte2) { + return; + } + if (buffer->sel.line1 >= 0) { + int l1_new = line1, l2_new = line2; + int b1_new = byte1, b2_new = byte2; + sort_selection(&buffer->sel.line1, &buffer->sel.byte1, &buffer->sel.line2, &buffer->sel.byte2); + sort_selection(&l1_new, &b1_new, &l2_new, &b2_new); + if (buffer->sel.line1 > l2_new || buffer->sel.line2 < l1_new) { + for (int i = buffer->sel.line1; i <= buffer->sel.line2; i++) { + ledit_buffer_wipe_line_cursor_attrs(buffer, i); + } + } else { + for (int i = buffer->sel.line1; i < l1_new; i++) { + ledit_buffer_wipe_line_cursor_attrs(buffer, i); + } + for (int i = buffer->sel.line2; i > l2_new; i--) { + ledit_buffer_wipe_line_cursor_attrs(buffer, i); + } + } + if (l1_new == l2_new) { + ledit_buffer_set_line_selection(buffer, l1_new, b1_new, b2_new); + } else { + ledit_line *ll1 = ledit_buffer_get_line(buffer, l1_new); + ledit_buffer_set_line_selection(buffer, l1_new, b1_new, ll1->len); + ledit_buffer_set_line_selection(buffer, l2_new, 0, b2_new); + /* FIXME: optimize this */ + for (int i = l1_new + 1; i < l2_new; i++) { + if (i <= buffer->sel.line1 || i >= buffer->sel.line2) { + ledit_line *llx = ledit_buffer_get_line(buffer, i); + ledit_buffer_set_line_selection(buffer, i, 0, llx->len); + } + } + } + copy_selection_to_x_primary(buffer, l1_new, b1_new, l2_new, b2_new); + } + buffer->sel.line1 = line1; + buffer->sel.byte1 = byte1; + buffer->sel.line2 = line2; + buffer->sel.byte2 = byte2; +} + +void +ledit_buffer_scroll_handler(void *buffer, long pos) { + ((ledit_buffer *)buffer)->display_offset = pos; +} + +void +ledit_buffer_button_handler(void *data, XEvent *event) { + int l, b; + ledit_buffer *buffer = (ledit_buffer *)data; + int x = event->xbutton.x; + int y = event->xbutton.y; + switch (event->type) { + case ButtonPress: + ledit_xy_to_line_byte(buffer, x, y, &l, &b); + ledit_buffer_set_selection(buffer, l, b, l, b); + if (buffer->common->mode == NORMAL) { + ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + /* FIXME: only set mode after dragging/when something is selected? */ + /* -> return to old mode afterwards? */ + /* should change_mode_group even be called here? */ + ledit_buffer_set_mode(buffer, VISUAL); + } + buffer->cur_line = l; + buffer->cur_index = b; + buffer->selecting = 1; + break; + case ButtonRelease: + buffer->selecting = 0; + break; + case MotionNotify: + if (buffer->selecting) { + y = y >= 0 ? y : 0; + ledit_xy_to_line_byte(buffer, x, y, &l, &b); + ledit_buffer_set_selection(buffer, buffer->sel.line1, buffer->sel.byte1, l, b); + buffer->cur_line = l; + buffer->cur_index = b; + } + break; + } +} + +void +ledit_buffer_redraw(ledit_buffer *buffer) { + int h = 0; + int cur_line_y = 0; + int cursor_displayed = 0; + int text_w, text_h; + ledit_window_get_textview_size(buffer->window, &text_w, &text_h); + for (int i = 0; i < buffer->lines_num; i++) { + ledit_line *line = ledit_buffer_get_line(buffer, i); + if (h + line->h > buffer->display_offset) { + if (line->dirty || line->cache_index == -1) { + ledit_buffer_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 > text_h) { + final_h -= final_y + final_h - + buffer->display_offset - text_h; + } + ledit_cache_pixmap *pix = ledit_get_cache_pixmap( + buffer->cache, line->cache_index + ); + XCopyArea( + buffer->common->dpy, pix->pixmap, + buffer->window->drawable, buffer->window->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 + text_h) + break; + h += line->h; + } + + XSetForeground(buffer->common->dpy, buffer->window->gc, buffer->theme->text_fg.pixel); + PangoRectangle strong, weak; + ledit_line *cur_line = ledit_buffer_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 (buffer->common->mode == NORMAL && buffer->cur_index == cur_line->len) { + XFillRectangle( + buffer->common->dpy, buffer->window->drawable, buffer->window->gc, + strong.x / PANGO_SCALE, cursor_y, + 10, strong.height / PANGO_SCALE + ); + } else if (buffer->common->mode == INSERT || buffer->common->mode == VISUAL) { + XDrawLine( + buffer->common->dpy, buffer->window->drawable, buffer->window->gc, + strong.x / PANGO_SCALE, cursor_y, + strong.x / PANGO_SCALE, + (strong.y + strong.height) / PANGO_SCALE + cur_line_y + ); + } + } + /* move input method position */ + if (!ledit_window_bottom_bar_text_shown(buffer->window)) { + xximspot( + buffer->window, + strong.x / PANGO_SCALE, + (strong.y + strong.height) / PANGO_SCALE + cur_line_y + ); + } +} + +static void +undo_insert_helper(void *data, int line, int byte, char *text, int text_len) { + ledit_buffer_insert_text_with_newlines_base((ledit_buffer *)data, line, byte, text, text_len, NULL, NULL); +} + +static void +undo_delete_helper(void *data, int line1, int byte1, int line2, int byte2) { + ledit_buffer_delete_range_base((ledit_buffer *)data, 0, line1, byte1, line2, byte2, NULL, NULL, NULL, NULL); +} + +void +ledit_buffer_undo(ledit_buffer *buffer) { + int min_line; + ledit_undo( + buffer->undo, buffer->common->mode, buffer, &undo_insert_helper, + &undo_delete_helper, &buffer->cur_line, &buffer->cur_index, &min_line + ); + if (buffer->common->mode == NORMAL) { + buffer->cur_index = ledit_buffer_get_legal_normal_pos( + buffer, buffer->cur_line, buffer->cur_index + ); + } + if (min_line < buffer->lines_num) + ledit_buffer_recalc_from_line(buffer, min_line > 0 ? min_line - 1 : min_line); + /* FIXME: show undo message */ +} + +void +ledit_buffer_redo(ledit_buffer *buffer) { + int min_line; + ledit_redo( + buffer->undo, buffer->common->mode, buffer, &undo_insert_helper, + &undo_delete_helper, &buffer->cur_line, &buffer->cur_index, &min_line + ); + if (buffer->common->mode == NORMAL) { + buffer->cur_index = ledit_buffer_get_legal_normal_pos( + buffer, buffer->cur_line, buffer->cur_index + ); + } + if (min_line < buffer->lines_num) + ledit_buffer_recalc_from_line(buffer, min_line > 0 ? min_line - 1 : min_line); + /* FIXME: show undo message */ +} + +static void +paste_callback(void *data, char *text, int len) { + ledit_buffer *buffer = (ledit_buffer *)data; + ledit_buffer_insert_text_with_newlines( + buffer, buffer->cur_line, buffer->cur_index, + text, len, &buffer->cur_line, &buffer->cur_index + ); +} + +/* FIXME: guard against buffer being destroyed before paste callback is nulled */ + +void +ledit_buffer_paste_clipboard(ledit_buffer *buffer) { + ledit_window_set_paste_callback(buffer->window, &paste_callback, buffer); + clipboard_paste_clipboard(buffer->window); +} + +void +ledit_buffer_paste_primary(ledit_buffer *buffer) { + ledit_window_set_paste_callback(buffer->window, &paste_callback, buffer); + clipboard_paste_primary(buffer->window); +} diff --git a/buffer.h b/buffer.h @@ -1,10 +1,3 @@ -typedef struct { - int line1; - int byte1; - int line2; - int byte2; -} ledit_range; - typedef struct ledit_buffer ledit_buffer; /* FIXME: size_t for len, etc. */ @@ -27,8 +20,9 @@ typedef struct { /* TODO: advisory lock on file? also check if modification date changed before writing */ struct ledit_buffer { - ledit_common_state *state; /* general state, e.g. display, window, etc. */ + ledit_common *common; /* common stuff, e.g. display, window, etc. */ ledit_line *lines; /* array of lines */ + ledit_theme *theme; char *filename; int lines_cap; /* number of lines allocated in array */ int lines_num; /* number of used lines */ @@ -41,37 +35,40 @@ struct ledit_buffer { 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 */ + int selecting; ledit_range sel; /* current selection; all entries -1 if no selection */ + ledit_cache *cache; ledit_undo_stack *undo; + ledit_window *window; }; -ledit_buffer *ledit_create_buffer(ledit_common_state *state); -int ledit_load_file_into_buffer(ledit_buffer *buffer, char *filename, int line, char **errstr); -int ledit_write_buffer_to_file(ledit_buffer *buffer, char *filename, char **errstr); -void ledit_destroy_buffer(ledit_buffer *buffer); -void ledit_normalize_line(ledit_line *line); -void ledit_set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end_byte); -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_render_line(ledit_buffer *buffer, int line_index); -ledit_line *ledit_get_line(ledit_buffer *buffer, int index); -int ledit_line_visible(ledit_buffer *buffer, int index); -int ledit_get_legal_normal_pos(ledit_buffer *buffer, int line, int pos); +ledit_buffer *ledit_buffer_create(ledit_common *common, ledit_theme *theme, ledit_window *window); +int ledit_buffer_load_file(ledit_buffer *buffer, char *filename, int line, char **errstr); +int ledit_buffer_write_to_file(ledit_buffer *buffer, char *filename, char **errstr); +void ledit_buffer_destroy(ledit_buffer *buffer); +void ledit_buffer_normalize_line(ledit_line *line); +void ledit_buffer_set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end_byte); +void ledit_buffer_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index); +void ledit_buffer_wipe_line_cursor_attrs(ledit_buffer *buffer, int line); +void ledit_buffer_render_line(ledit_buffer *buffer, int line_index); +ledit_line *ledit_buffer_get_line(ledit_buffer *buffer, int index); +int ledit_buffer_line_visible(ledit_buffer *buffer, int index); +int ledit_buffer_get_legal_normal_pos(ledit_buffer *buffer, int line, int pos); void ledit_pos_to_x_softline(ledit_line *line, int pos, int *x_ret, int *softline_ret); void ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret); -int ledit_next_utf8(ledit_line *line, int index); -int ledit_prev_utf8(ledit_line *line, int index); -size_t ledit_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2); -void ledit_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2); -void ledit_copy_text_to_lbuf( +int ledit_line_next_utf8(ledit_line *line, int index); +int ledit_line_prev_utf8(ledit_line *line, int index); +size_t ledit_buffer_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2); +void ledit_buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2); +void ledit_buffer_copy_text_to_txtbuf( ledit_buffer *buffer, - lbuf *buf, /* oh, isn't that a very non-confusing name? */ + txtbuf *buf, /* oh, isn't that a very non-confusing name? */ int line1, int byte1, int line2, int byte2 ); -void ledit_recalc_line(ledit_buffer *buffer, int line); -void ledit_recalc_from_line(ledit_buffer *buffer, int line); -void ledit_recalc_all_lines(ledit_buffer *buffer); +void ledit_buffer_recalc_line(ledit_buffer *buffer, int line); +void ledit_buffer_recalc_from_line(ledit_buffer *buffer, int line); +void ledit_buffer_recalc_all_lines(ledit_buffer *buffer); /* The following functions all have two versions: * - The _base version does not call any recalc functions - this can be used @@ -80,52 +77,66 @@ void ledit_recalc_all_lines(ledit_buffer *buffer); * - The non-base versions call the appropriate recalc function in order to * keep everything in a consistent state. */ -void ledit_insert_text_base(ledit_buffer *buffer, int line_index, int index, char *text, int len); -void ledit_insert_text_with_newlines_base( +void ledit_buffer_insert_text_base(ledit_buffer *buffer, int line_index, int index, char *text, int len); +void ledit_buffer_insert_text_with_newlines_base( ledit_buffer *buffer, int line_index, int index, char *text, long len, int *end_line_ret, int *end_char_ret ); -void ledit_append_line_base(ledit_buffer *buffer, int line_index, int text_index); -void ledit_delete_line_entries_base(ledit_buffer *buffer, int index1, int index2); -void ledit_delete_line_entry_base(ledit_buffer *buffer, int index); -int ledit_delete_unicode_char_base(ledit_buffer *buffer, int line_index, int byte_index, int dir); -void ledit_delete_range_base( +void ledit_buffer_append_line_base(ledit_buffer *buffer, int line_index, int text_index); +void ledit_buffer_delete_line_entries_base(ledit_buffer *buffer, int index1, int index2); +void ledit_buffer_delete_line_entry_base(ledit_buffer *buffer, int index); +int ledit_buffer_delete_unicode_char_base(ledit_buffer *buffer, int line_index, int byte_index, int dir); +void ledit_buffer_delete_range_base( ledit_buffer *buffer, int line_based, int line_index1, int byte_index1, int line_index2, int byte_index2, int *new_line_ret, int *new_byte_ret, - ledit_range *final_range_ret, lbuf *text_ret + ledit_range *final_range_ret, txtbuf *text_ret ); -void ledit_insert_text_from_line_base( +void ledit_buffer_insert_text_from_line_base( ledit_buffer *buffer, int dst_line, int dst_index, int src_line, int src_index, int src_len, - lbuf *text_ret + txtbuf *text_ret ); -void ledit_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, int len); -void ledit_insert_text_with_newlines( +void ledit_buffer_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, int len); +void ledit_buffer_insert_text_with_newlines( ledit_buffer *buffer, int line_index, int index, char *text, long len, int *end_line_ret, int *end_char_ret ); -void ledit_append_line(ledit_buffer *buffer, int line_index, int text_index); -void ledit_delete_line_entries(ledit_buffer *buffer, int index1, int index2); -void ledit_delete_line_entry(ledit_buffer *buffer, int index); -int ledit_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index, int dir); -void ledit_delete_range( +void ledit_buffer_append_line(ledit_buffer *buffer, int line_index, int text_index); +void ledit_buffer_delete_line_entries(ledit_buffer *buffer, int index1, int index2); +void ledit_buffer_delete_line_entry(ledit_buffer *buffer, int index); +int ledit_buffer_delete_unicode_char(ledit_buffer *buffer, int line_index, int byte_index, int dir); +void ledit_buffer_delete_range( ledit_buffer *buffer, int line_based, int line_index1, int byte_index1, int line_index2, int byte_index2, int *new_line_ret, int *new_byte_ret, - ledit_range *final_range_ret, lbuf *text_ret + ledit_range *final_range_ret, txtbuf *text_ret ); -void ledit_insert_text_from_line( +void ledit_buffer_insert_text_from_line( ledit_buffer *buffer, int dst_line, int dst_index, int src_line, int src_index, int src_len, - lbuf *text_ret + txtbuf *text_ret ); + +void ledit_buffer_resize_width(ledit_buffer *buffer, int width); +void ledit_xy_to_line_byte(ledit_buffer *buffer, int x, int y, int *line_ret, int *byte_ret); +void ledit_buffer_ensure_cursor_shown(ledit_buffer *buffer); +void ledit_buffer_scroll_handler(void *buffer, long pos); +void ledit_buffer_button_handler(void *data, XEvent *event); +void ledit_buffer_redraw(ledit_buffer *buffer); +void ledit_buffer_undo(ledit_buffer *buffer); +void ledit_buffer_redo(ledit_buffer *buffer); +void ledit_buffer_set_selection(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2); +void ledit_buffer_set_mode(ledit_buffer *buffer, enum ledit_mode mode); +void ledit_buffer_paste_clipboard(ledit_buffer *buffer); +void ledit_buffer_paste_primary(ledit_buffer *buffer); +void ledit_buffer_resize_textview(ledit_buffer *buffer); diff --git a/cache.c b/cache.c @@ -6,94 +6,76 @@ #include "common.h" #include "memory.h" -#include "lbuf.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; +ledit_cache * +ledit_cache_create(ledit_common *common) { + (void)common; /* FIXME: remove argument */ /* FIXME: prevent overflow */ - cache.entries = ledit_malloc(20 * sizeof(ledit_cache_pixmap)); + ledit_cache *cache = ledit_malloc(sizeof(ledit_cache)); + 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[i].pixmap = None; + cache->entries[i].draw = NULL; + cache->entries[i].line = -1; } - cache.entries_num = 20; - cache.cur_replace_index = -1; + cache->entries_num = 20; + cache->cur_replace_index = -1; + return cache; } void -ledit_flush_cache(void) { - for (int i = 0; i < cache.entries_num; i++) { - cache.entries[i].line = -1; +ledit_cache_flush(ledit_cache *cache) { + 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); +ledit_cache_destroy(ledit_cache *cache) { + for (int i = 0; i < cache->entries_num; i++) { + if (cache->entries[i].pixmap != None) + XFreePixmap(cache->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; + free(cache->entries); + free(cache); } -void -ledit_assign_free_cache_index(ledit_buffer *buffer, int new_line_index) { +/* returns a cache index that is currently not needed (if needed, the cache size is increased) + whether it is needed or not is checked with line_needed, to which + callback_data is always passed as the first argument */ +int +ledit_get_unneeded_cache_index(ledit_cache *cache, void *callback_data, int (*line_needed)(void *, int)) { 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; + 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; + !line_needed(callback_data, line_index))) { + cache->cur_replace_index = entry_index; + return entry_index; } } /* 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 = 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; + cache->entries_num *= 2; + return entry_index; } ledit_cache_pixmap * -ledit_get_cache_pixmap(int index) { - return &cache.entries[index]; +ledit_get_cache_pixmap(ledit_cache *cache, int index) { + return &cache->entries[index]; } diff --git a/cache.h b/cache.h @@ -5,8 +5,15 @@ typedef struct { 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); +typedef struct { + Display *dpy; + ledit_cache_pixmap *entries; + int entries_num; + int cur_replace_index; +} ledit_cache; + +ledit_cache *ledit_cache_create(ledit_common *common); +void ledit_cache_flush(ledit_cache *cache); +void ledit_cache_destroy(ledit_cache *cache); +ledit_cache_pixmap *ledit_get_cache_pixmap(ledit_cache *cache, int index); +int ledit_get_unneeded_cache_index(ledit_cache *cache, void *callback_data, int (*line_needed)(void *, int)); diff --git a/commands.c b/commands.c @@ -1,168 +0,0 @@ -/* FIXME: Parse commands properly and allow combinations of commands */ -#include <ctype.h> -#include <stdlib.h> -#include <X11/Xlib.h> -#include <X11/Xutil.h> -#include <pango/pangoxft.h> -#include <X11/extensions/Xdbe.h> - -#include "memory.h" -#include "common.h" -#include "lbuf.h" -#include "buffer.h" - -static int -handle_write(ledit_buffer *buffer, char *cmd, int l1, int l2) { - (void)buffer; - (void)cmd; - (void)l1; - (void)l2; - /* FIXME: Implement properly; handle error */ - char *errstr; - if (buffer->filename) - ledit_write_buffer_to_file(buffer, buffer->filename, &errstr); - return 0; -} - -static int -handle_quit(ledit_buffer *buffer, char *cmd, int l1, int l2) { - (void)buffer; - (void)cmd; - (void)l1; - (void)l2; - /* FIXME: Implement */ - exit(1); - return 0; -} - -static int -handle_write_quit(ledit_buffer *buffer, char *cmd, int l1, int l2) { - (void)buffer; - (void)cmd; - (void)l1; - (void)l2; - printf("write quit\n"); - return 0; -} - -static int -handle_substitute(ledit_buffer *buffer, char *cmd, int l1, int l2) { - (void)buffer; - (void)cmd; - (void)l1; - (void)l2; - printf("substitute\n"); - return 0; -} - -enum cmd_type { - CMD_NORMAL, - CMD_OPTIONAL_RANGE -}; - -static const struct { - char *cmd; - enum cmd_type type; - int (*handler)(ledit_buffer *buffer, char *cmd, int l1, int l2); -} cmds[] = { - {"wq", CMD_OPTIONAL_RANGE, &handle_write_quit}, - {"w", CMD_OPTIONAL_RANGE, &handle_write}, - {"q", CMD_NORMAL, &handle_quit}, - {"s", CMD_OPTIONAL_RANGE, &handle_substitute} -}; - -#define LENGTH(X) (sizeof(X) / sizeof(X[0])) - -/* -. current line - FIXME: implement -$ last line -% all lines -*/ - -/* FIXME: ACTUALLY USE LEN!!! */ -static int -parse_range(ledit_buffer *buffer, char *cmd, int len, char **cmd_ret, int *line1_ret, int *line2_ret) { - (void)len; - enum { - START_LINENO = 1, - START_RANGE = 2, - IN_RANGE = 4, - IN_LINENO = 8 - } s = START_LINENO | START_RANGE; - int l1 = -1, l2 = -1; - char *c = cmd; - for (; *c != '\0'; c++) { - if (isdigit(*c)) { - /* FIXME: integer overflow */ - if (s & IN_LINENO) { - if (l2 != -1) - l2 = l2 * 10 + (*c - '0'); - else - l1 = l1 * 10 + (*c - '0'); - } else if ((s & START_LINENO) && (s & START_RANGE)) { - l1 = *c - '0'; - s &= ~START_RANGE; - s &= ~START_LINENO; - s |= IN_RANGE | IN_LINENO; - } else if ((s & START_LINENO)) { - l2 = *c - '0'; - s &= ~START_LINENO; - s |= IN_LINENO; - } - } else if (*c == ',' && !(s & START_RANGE)) { - if (l1 != -1 && l2 != -1) { - return 1; - } else { - s |= START_LINENO; - s &= ~IN_LINENO; - } - } else if (*c == '%') { - if (s & START_RANGE) { - l1 = 1; - l2 = buffer->lines_num; - break; - } else { - return 1; - } - } else if (*c == '$') { - if (s & START_LINENO) { - if (l1 == -1) - l1 = buffer->lines_num; - else - l2 = buffer->lines_num; - s &= ~START_LINENO; - s &= ~IN_LINENO; - } else { - return 1; - } - } else { - break; - } - } - if ((l1 == -1 || l2 == -1) && !(s & START_RANGE)) - return 1; - *cmd_ret = c; - *line1_ret = l1; - *line2_ret = l2; - return 0; -} - -int -ledit_handle_cmd(ledit_buffer *buffer, char *cmd, int len) { - if (len < 0) - len = strlen(cmd); - if (len < 1) - return 1; - char *c; - int l1, l2; - if (parse_range(buffer, cmd, len, &c, &l1, &l2)) - return 1; - int range_given = l1 != -1 && l2 != -1; - 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(buffer, c, l1, l2); - } - } - return 1; -} diff --git a/commands.h b/commands.h @@ -1 +0,0 @@ -int ledit_handle_cmd(ledit_buffer *buffer, char *cmd, int len); diff --git a/common.h b/common.h @@ -1,41 +1,22 @@ -/* FIXME: it's ugly to put this here */ -typedef struct ledit_undo_stack ledit_undo_stack; +typedef struct { + int line1; + int byte1; + int line2; + int byte2; +} ledit_range; + enum ledit_mode { NORMAL = 1, INSERT = 2, - VISUAL = 4, - COMMANDEDIT = 8, - SEARCHEDIT = 16 + 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 text_w; - int text_h; - int scroll_dragging; - int scroll_grab_handle; - int selecting; - int bottom_text_shown; - int message_shown; + int redraw; enum ledit_mode mode; - XIM xim; - XIC xic; - XftColor fg; - XftColor bg; - XftColor scroll_bg; - XSetWindowAttributes wattrs; - Atom wm_delete_msg; -} ledit_common_state; +} ledit_common; diff --git a/keys.c b/keys.c @@ -0,0 +1,69 @@ +#include <limits.h> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/keysym.h> +#include <X11/XF86keysym.h> +#include <pango/pangoxft.h> +#include <X11/extensions/Xdbe.h> + +#include "memory.h" +#include "common.h" +#include "txtbuf.h" +#include "theme.h" +#include "window.h" +#include "keys.h" + +static char *key_langs[] = { + "English (US)", + "German", + "Urdu (Pakistan)", + "Hindi (Bolnagri)" +}; + +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; +#define XK_ANY_MOD UINT_MAX + +int +match_key(unsigned int mask, unsigned int state) +{ + return mask == XK_ANY_MOD || mask == (state & importantmod); +} + +void +preprocess_key( + ledit_window *window, XEvent *event, KeySym *sym_ret, + char *buf_ret, int buf_size, int *buf_len_ret) { + /* + * FIXME: I don't understand how key handling works with different keymaps. + * If, for instance, you run "setxkbmap pk" and then type "Ctrl+c", the + * keysym will be "0x1000686, Arabic_tcheh" and the appropriate string will + * be returned by XmbLookupString (tested also using xev). + * If, however, you run "setxkbmap -layout "us,pk" -option grp:shifts_toggle" + * and type "Ctrl+c" after switching to the pk layout, the keysym will just + * be "0x63, c" and XmbLookupString will return the control code sent by + * Ctrl+c (ascii 03). This means the handling is different depending on how + * the keymap is switched to, and I have no clue what the proper way to + * handle this would be, since the shortcuts are explicitly supposed to work + * properly in all language maps. My current solution is to just ignore the + * control key in the state so the text found by Xutf8LookupString is correct + * and the modifier mask can be checked separately. Please tell me if you + * know the proper way to do this. + */ + event->xkey.state &= ~ControlMask; + /* FIXME: X_HAVE_UTF8_STRING See XmbLookupString(3) */ + *buf_len_ret = Xutf8LookupString( + window->xic, &event->xkey, buf_ret, buf_size, sym_ret, NULL + ); +} diff --git a/keys.h b/keys.h @@ -0,0 +1,23 @@ +#define LENGTH(X) (sizeof(X) / sizeof(X[0])) + +/* IMPORTANT: Also edit key_langs in keys.c when changing languages! */ + +#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] + +int get_language_index(char *lang); +int match_key(unsigned int mask, unsigned int state); +void preprocess_key( + ledit_window *window, XEvent *event, KeySym *sym_ret, + char *buf_ret, int buf_size, int *buf_len_ret +); diff --git a/keys_basic.c b/keys_basic.c @@ -0,0 +1,937 @@ +#include <stdio.h> +#include <stdlib.h> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <pango/pangoxft.h> +#include <X11/extensions/Xdbe.h> +#include <X11/keysym.h> +#include <X11/XF86keysym.h> +#include <X11/cursorfont.h> + +#include "memory.h" +#include "common.h" +#include "txtbuf.h" +#include "undo.h" +#include "cache.h" +#include "theme.h" +#include "window.h" +#include "buffer.h" +#include "search.h" + +#include "keys.h" +#include "action.h" +#include "keys_basic.h" +#include "keys_command.h" +#include "keys_basic_config.h" + +/* this is supposed to be global for all buffers */ +txtbuf *paste_buffer = NULL; + +struct key_stack_elem { + enum key_type key; + enum key_type followup; /* allowed keys to complete the keybinding */ + /* callback function that motion commands call to complete a command - + * line and char_pos already include the repetition stored in this stack + * element; type is the type of motion command (used to determine if + * the command should operate on lines or chars) */ + void (*motion_cb)(ledit_buffer *buffer, int line, int char_pos, enum key_type type); + int count; /* number of repetitions */ + int data1; /* misc. data 1 */ + int data2; /* misc. data 2 */ +}; + +static struct { + size_t len, alloc; + struct key_stack_elem *stack; +} key_stack = {0, 0, NULL}; + +static struct key_stack_elem *push_key_stack(void); +static struct key_stack_elem *peek_key_stack(void); +static struct key_stack_elem *pop_key_stack(void); +void clear_key_stack(void); + +static void move_cursor_left_right(ledit_buffer *buffer, int dir); +static void move_cursor_up_down(ledit_buffer *buffer, int dir); +static void push_num(int num); +static void key_d_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type); +static void get_new_line_softline( + ledit_buffer *buffer, int cur_line, int cur_index, + int movement, int *new_line_ret, int *new_softline_ret +); + +/* FIXME: move to common */ +static void +swap(int *a, int *b) { + int tmp = *a; + *a = *b; + *b = tmp; +} + +static struct key_stack_elem * +push_key_stack(void) { + struct key_stack_elem *e; + if (key_stack.len >= key_stack.alloc) { + size_t new_alloc = key_stack.alloc > 0 ? key_stack.alloc * 2 : 4; + key_stack.stack = ledit_realloc( + key_stack.stack, new_alloc * sizeof(struct key_stack_elem) + ); + key_stack.alloc = new_alloc; + } + e = &key_stack.stack[key_stack.len]; + e->key = KEY_NONE; + e->followup = KEY_NONE; + e->motion_cb = NULL; + e->count = 0; + e->data1 = 0; + e->data2 = 0; + key_stack.len++; + return &key_stack.stack[key_stack.len - 1]; +} + +/* Note: for peek and pop, the returned element is only valid + * until the next element is pushed */ +/* Note on the note: that's not entirely true for peek */ +static struct key_stack_elem * +peek_key_stack(void) { + if (key_stack.len > 0) + return &key_stack.stack[key_stack.len - 1]; + return NULL; +} + +static struct key_stack_elem * +pop_key_stack(void) { + if (key_stack.len > 0) { + key_stack.len--; + return &key_stack.stack[key_stack.len]; + } + return NULL; +} + +void +clear_key_stack(void) { + key_stack.len = 0; +} + +/* get the new line and softline when moving 'movement' softlines up or + down (negative means up, positive means down) */ +static void +get_new_line_softline( + ledit_buffer *buffer, int cur_line, int cur_index, int movement, + int *new_line_ret, int *new_softline_ret) { + ledit_line *line = ledit_buffer_get_line(buffer, cur_line); + int x, softline; + pango_layout_index_to_line_x(line->layout, cur_index, 0, &softline, &x); + if (movement > 0) { + int softlines = pango_layout_get_line_count(line->layout); + if (softlines - softline > movement) { + *new_line_ret = cur_line; + *new_softline_ret = softline + movement; + } else { + movement -= (softlines - softline - 1); + int endline = cur_line + 1; + while (movement > 0 && endline < buffer->lines_num) { + line = ledit_buffer_get_line(buffer, endline); + softlines = pango_layout_get_line_count(line->layout); + movement -= softlines; + endline++; + } + endline--; + if (movement <= 0) { + *new_softline_ret = movement + softlines - 1; + } else { + *new_softline_ret = softlines - 1; + } + *new_line_ret = endline; + } + } else if (movement < 0) { + int softlines = 0; + if (softline + movement >= 0) { + *new_line_ret = cur_line; + *new_softline_ret = softline + movement; + } else { + movement += softline; + int endline = cur_line - 1; + while (movement < 0 && endline >= 0) { + line = ledit_buffer_get_line(buffer, endline); + softlines = pango_layout_get_line_count(line->layout); + movement += softlines; + endline--; + } + endline++; + if (movement >= 0) { + *new_softline_ret = movement; + } else { + *new_softline_ret = 0; + } + *new_line_ret = endline; + } + } else { + *new_line_ret = cur_line; + *new_softline_ret = softline; + } +} + +/* FIXME: don't overwrite buffer->cur_line, etc. here? */ +static void +delete_range( + ledit_buffer *buffer, + int line_based, int selected, + int line_index1, int byte_index1, + int line_index2, int byte_index2) { + (void)selected; /* FIXME */ + if (!paste_buffer) + paste_buffer = txtbuf_new(); + ledit_range cur_range, del_range; + cur_range.line1 = buffer->cur_line; + cur_range.byte1 = buffer->cur_index; + ledit_buffer_delete_range( + buffer, line_based, + line_index1, byte_index1, + line_index2, byte_index2, + &buffer->cur_line, &buffer->cur_index, + &del_range, paste_buffer + ); + cur_range.line2 = buffer->cur_line; + cur_range.byte2 = buffer->cur_index; + ledit_push_undo_delete( + buffer->undo, paste_buffer, del_range, cur_range, 1, buffer->common->mode + ); +} + +static void +insert_text(ledit_buffer *buffer, int line, int index, char *text, int len, int start_group) { + if (len < 0) + len = strlen(text); + /* FIXME: this is kind of hacky... */ + txtbuf ins_buf = {.text = text, .len = len, .cap = len}; + ledit_range cur_range, del_range; + cur_range.line1 = buffer->cur_line; + cur_range.byte1 = buffer->cur_index; + del_range.line1 = line; + del_range.byte1 = index; + ledit_buffer_insert_text_with_newlines( + buffer, line, index, text, len, + &buffer->cur_line, &buffer->cur_index + ); + cur_range.line2 = buffer->cur_line; + cur_range.byte2 = buffer->cur_index; + del_range.line2 = buffer->cur_line; + del_range.byte2 = buffer->cur_index; + ledit_push_undo_insert( + buffer->undo, &ins_buf, del_range, cur_range, start_group, buffer->common->mode + ); +} + +static int +delete_selection(ledit_buffer *buffer) { + if (buffer->sel.line1 != buffer->sel.line2 || buffer->sel.byte1 != buffer->sel.byte2) { + delete_range( + buffer, 0, 0, + buffer->sel.line1, buffer->sel.byte1, + buffer->sel.line2, buffer->sel.byte2 + ); + /* FIXME: maybe just set this to the current cursor pos? */ + buffer->sel.line1 = buffer->sel.line2 = -1; + buffer->sel.byte1 = buffer->sel.byte2 = -1; + ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + return 1; + } + return 0; +} + +static struct action +key_d(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + int num = 0; + if (delete_selection(buffer)) { + ledit_buffer_set_mode(buffer, NORMAL); + buffer->cur_index = ledit_buffer_get_legal_normal_pos(buffer, buffer->cur_line, buffer->cur_index); + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + clear_key_stack(); + } else { + struct key_stack_elem *e = pop_key_stack(); + if (e != NULL) { + if (e->key & KEY_NUMBER) { + num = e->count; + e = pop_key_stack(); + } + /* FIXME: checking equality of the function pointer may be a bit risky */ + if (e != NULL && e->motion_cb == &key_d_cb) { + int prevnum = e->count > 0 ? e->count : 1; + num = num > 0 ? num : 1; + int lines = num * prevnum; + int new_line, new_softline; + get_new_line_softline( + buffer, buffer->cur_line, buffer->cur_index, + lines - 1, &new_line, &new_softline + ); + ledit_line *ll = ledit_buffer_get_line(buffer, new_line); + PangoLayoutLine *pl = pango_layout_get_line_readonly(ll->layout, new_softline); + e->motion_cb(buffer, new_line, pl->start_index, KEY_MOTION_LINE); + clear_key_stack(); + } else if (e != NULL) { + clear_key_stack(); + } + } + if (e == NULL) { + e = push_key_stack(); + e->key = KEY_MOTION; /* ? */ + e->count = num; + e->motion_cb = &key_d_cb; + } + } + return (struct action){ACTION_NONE, NULL}; +} + +/* FIXME: should this get number of lines to remove or actual end line? */ +static void +key_d_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type) { + int line_based = type == KEY_MOTION_LINE ? 1 : 0; + delete_range( + buffer, line_based, 0, + buffer->cur_line, buffer->cur_index, + line, char_pos + ); + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); +} + +static struct action +key_x(ledit_buffer *buffer, char *text, int len) { + (void)buffer; + (void)text; + (void)len; + struct key_stack_elem *e = pop_key_stack(); + int num = 1; + clear_key_stack(); + if (e && !(e->key & KEY_NUMBER)) + return (struct action){ACTION_NONE, NULL}; + if (e) + num = e->count; + if (num <= 0) + num = 1; + /* FIXME: actually do something */ + return (struct action){ACTION_NONE, NULL}; +} + +static void +push_num(int num) { + struct key_stack_elem *e = peek_key_stack(); + if (!e || !(e->key & KEY_NUMBER)) { + e = push_key_stack(); + e->key = KEY_NUMBER; + e->followup = KEY_NUMBER|KEY_NUMBERALLOWED; + } + /* FIXME: error (overflow) checking */ + e->count *= 10; + e->count += num; +} + +static struct action +push_0(ledit_buffer *buffer, char *text, int len) { + (void)buffer; + (void)text; + (void)len; + push_num(0); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +push_1(ledit_buffer *buffer, char *text, int len) { + (void)buffer; + (void)text; + (void)len; + push_num(1); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +push_2(ledit_buffer *buffer, char *text, int len) { + (void)buffer; + (void)text; + (void)len; + push_num(2); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +push_3(ledit_buffer *buffer, char *text, int len) { + (void)buffer; + (void)text; + (void)len; + push_num(3); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +push_4(ledit_buffer *buffer, char *text, int len) { + (void)buffer; + (void)text; + (void)len; + push_num(4); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +push_5(ledit_buffer *buffer, char *text, int len) { + (void)buffer; + (void)text; + (void)len; + push_num(5); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +push_6(ledit_buffer *buffer, char *text, int len) { + (void)buffer; + (void)text; + (void)len; + push_num(6); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +push_7(ledit_buffer *buffer, char *text, int len) { + (void)buffer; + (void)text; + (void)len; + push_num(7); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +push_8(ledit_buffer *buffer, char *text, int len) { + (void)buffer; + (void)text; + (void)len; + push_num(8); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +push_9(ledit_buffer *buffer, char *text, int len) { + (void)buffer; + (void)text; + (void)len; + push_num(9); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +backspace(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + if (delete_selection(buffer)) { + /* NOP */ + } else if (buffer->cur_index == 0) { + if (buffer->cur_line != 0) { + ledit_line *l1 = ledit_buffer_get_line(buffer, buffer->cur_line - 1); + delete_range(buffer, 0, 0, buffer->cur_line - 1, l1->len, buffer->cur_line, 0); + } + } else { + ledit_line *l = ledit_buffer_get_line(buffer, buffer->cur_line); + int i = ledit_line_prev_utf8(l, buffer->cur_index); + delete_range(buffer, 0, 0, buffer->cur_line, buffer->cur_index, buffer->cur_line, i); + } + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +delete_key(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + ledit_line *cur_line = ledit_buffer_get_line(buffer, buffer->cur_line); + if (delete_selection(buffer)) { + /* NOP */ + } else if (buffer->cur_index == cur_line->len) { + if (buffer->cur_line != buffer->lines_num - 1) { + delete_range(buffer, 0, 0, buffer->cur_line, cur_line->len, buffer->cur_line + 1, 0); + } + } else { + int i = ledit_line_next_utf8(cur_line, buffer->cur_index); + delete_range(buffer, 0, 0, buffer->cur_line, buffer->cur_index, buffer->cur_line, i); + } + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + return (struct action){ACTION_NONE, NULL}; +} + +static void +move_cursor_left_right(ledit_buffer *buffer, int dir) { + int num = 1; + struct key_stack_elem *e = pop_key_stack(); + if (e != NULL) { + if (e->key & KEY_NUMBER) { + num = e->count > 0 ? e->count : 0; + e = pop_key_stack(); + } + if (e != NULL) + num *= (e->count > 0 ? e->count : 1); + } + + /* FIXME: trailing */ + int trailing = 0, tmp_index; + ledit_line *cur_line = ledit_buffer_get_line(buffer, buffer->cur_line); + int new_index = buffer->cur_index, last_index = buffer->cur_index; + while (num > 0) { + tmp_index = new_index; + pango_layout_move_cursor_visually( + cur_line->layout, TRUE, + new_index, trailing, dir, + &new_index, &trailing + ); + /* for some reason, this is necessary */ + if (new_index < 0) + new_index = 0; + else if (new_index > cur_line->len) + new_index = cur_line->len; + num--; + if (tmp_index != new_index) + last_index = tmp_index; + } + /* FIXME: Allow cursor to be at end of soft line */ + /* 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 */ + /* FIXME: spaces at end of softlines are weird in normal mode */ + while (trailing > 0) { + trailing--; + new_index = ledit_line_next_utf8(cur_line, new_index); + } + if (new_index < 0) + new_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 (new_index >= cur_line->len) { + if (buffer->common->mode == NORMAL && (e == NULL || e->motion_cb == NULL)) { + new_index = last_index; + } else { + /* FIXME: I guess this is unnecessary */ + new_index = cur_line->len; + } + } + if (e != NULL && e->motion_cb != NULL) { + e->motion_cb(buffer, buffer->cur_line, new_index, KEY_MOTION_CHAR); + } else { + buffer->cur_index = new_index; + if (buffer->common->mode == VISUAL) { + ledit_buffer_set_selection(buffer, buffer->sel.line1, buffer->sel.byte1, buffer->sel.line2, new_index); + } else if (buffer->common->mode == INSERT && + (buffer->sel.line1 != buffer->sel.line2 || + buffer->sel.byte1 != buffer->sel.byte2)) { + ledit_buffer_set_selection(buffer, buffer->cur_line, new_index, buffer->cur_line, new_index); + } else if (buffer->common->mode == NORMAL) { + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + } + } + clear_key_stack(); +} + +static struct action +cursor_left(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + move_cursor_left_right(buffer, -1); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +cursor_right(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + move_cursor_left_right(buffer, 1); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +return_key(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + int start_group = 1; + if (delete_selection(buffer)) + start_group = 0; + insert_text(buffer, buffer->cur_line, buffer->cur_index, "\n", -1, start_group); + /* FIXME: these aren't needed, right? This only works in insert mode + * anyways, so there's nothing to wipe */ + /* ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + buffer->cur_line++; + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + buffer->cur_index = 0; */ + return (struct action){ACTION_NONE, NULL}; +} + +/* FIXME: Check earliest version of other used functions to get minimum pango version + for ledit as a whole */ +/* FIXME: This is just copied from the newer version of pango. It *seems* like + the structs used are public (they're in the documentation), but it does seem + a bit dirty to do this here */ +#if !PANGO_VERSION_CHECK(1, 46, 0) +static PangoLayoutRun * +pango_layout_line_get_run(PangoLayoutLine *line, int index) { + GSList *run_list; + + run_list = line->runs; + while (run_list) { + PangoLayoutRun *run = run_list->data; + + if (run->item->offset <= index && run->item->offset + run->item->length > index) + return run; + + run_list = run_list->next; + } + + return NULL; +} + +static int +pango_layout_line_get_char_level(PangoLayoutLine *line, int index) { + PangoLayoutRun *run; + + run = pango_layout_line_get_run(line, index); + + if (run) + return run->item->analysis.level; + + return 0; +} + +static PangoDirection +pango_layout_line_get_char_direction(PangoLayoutLine *layout_line, int index) { + return pango_layout_line_get_char_level(layout_line, index) % 2 + ? PANGO_DIRECTION_RTL + : PANGO_DIRECTION_LTR; +} + +static PangoDirection +pango_layout_get_direction(PangoLayout *layout, int index) { + int lineno, x; + PangoLayoutLine *line; + pango_layout_index_to_line_x(layout, index, 0, &lineno, &x); + line = pango_layout_get_line_readonly(layout, lineno); + + if (line) + return pango_layout_line_get_char_direction(line, index); + + return PANGO_DIRECTION_LTR; +} +#endif + +static struct action +escape_key(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + clear_key_stack(); /* just in case... */ + if (buffer->common->mode == INSERT && + (buffer->sel.line1 != buffer->sel.line2 || + buffer->sel.byte1 != buffer->sel.byte2)) { + ledit_buffer_set_mode(buffer, VISUAL); + } else { + ledit_buffer_set_mode(buffer, NORMAL); + clear_key_stack(); + PangoDirection dir = PANGO_DIRECTION_RTL; + int tmp_index = buffer->cur_index; + ledit_line *cur_line = ledit_buffer_get_line(buffer, buffer->cur_line); + if (buffer->cur_index >= cur_line->len) + tmp_index--; + if (tmp_index >= 0) + dir = pango_layout_get_direction(cur_line->layout, tmp_index); + if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_WEAK_RTL) { + cursor_right(buffer, NULL, 0); + } else { + cursor_left(buffer, NULL, 0); + } + if (buffer->sel.line1 != buffer->sel.line2) { + int min = buffer->sel.line1 < buffer->sel.line2 ? buffer->sel.line1 : buffer->sel.line2; + int max = buffer->sel.line1 > buffer->sel.line2 ? buffer->sel.line1 : buffer->sel.line2; + for (int i = min; i <= max; i++) { + ledit_buffer_wipe_line_cursor_attrs(buffer, i); + } + } + /* FIXME: optimize this to avoid first wiping and then setting the attrs */ + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + } + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +enter_insert(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + if (buffer->common->mode == NORMAL) + ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + ledit_buffer_set_mode(buffer, INSERT); + clear_key_stack(); + return (struct action){ACTION_NONE, NULL}; +} + +/* FIXME: Check if previous key allows motion command - or is this checked automatically before? */ +static void +move_cursor_up_down(ledit_buffer *buffer, int dir) { + int new_line, new_softline; + + int num = 1; + struct key_stack_elem *e = pop_key_stack(); + if (e != NULL) { + if (e->key & KEY_NUMBER) { + num = e->count > 0 ? e->count : 0; + e = pop_key_stack(); + } + if (e != NULL) + num *= (e->count > 0 ? e->count : 1); + } + num *= dir; + + get_new_line_softline( + buffer, buffer->cur_line, buffer->cur_index, + num, &new_line, &new_softline + ); + + ledit_line *cur_lline = ledit_buffer_get_line(buffer, buffer->cur_line); + ledit_line *new_lline = ledit_buffer_get_line(buffer, new_line); + if (e != NULL && e->motion_cb != NULL) { + PangoLayoutLine *pl = pango_layout_get_line_readonly(new_lline->layout, new_softline); + e->motion_cb(buffer, new_line, pl->start_index, KEY_MOTION_LINE); + } else { + int lineno, x; + ledit_pos_to_x_softline(cur_lline, buffer->cur_index, &x, &lineno); + ledit_x_softline_to_pos(new_lline, x, new_softline, &buffer->cur_index); + if (buffer->cur_line != new_line) + ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + buffer->cur_line = new_line; + + if (buffer->common->mode == VISUAL) { + ledit_buffer_set_selection(buffer, buffer->sel.line1, buffer->sel.byte1, buffer->cur_line, buffer->cur_index); + } else if (buffer->common->mode == INSERT && + (buffer->sel.line1 != buffer->sel.line2 || + buffer->sel.byte1 != buffer->sel.byte2)) { + ledit_buffer_set_selection(buffer, buffer->cur_line, buffer->cur_index, buffer->cur_line, buffer->cur_index); + } else if (buffer->common->mode == NORMAL) { + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + } + } + clear_key_stack(); +} + +static struct action +cursor_down(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + move_cursor_up_down(buffer, 1); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +cursor_up(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + move_cursor_up_down(buffer, -1); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +cursor_to_beginning(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + struct key_stack_elem *e = pop_key_stack(); + /* FIXME: error when no callback? */ + if (e != NULL && e->motion_cb != NULL) { + e->motion_cb(buffer, buffer->cur_line, 0, KEY_MOTION_CHAR); + } else { + buffer->cur_index = 0; + if (buffer->common->mode == VISUAL) { + ledit_buffer_set_selection( + buffer, + buffer->sel.line1, buffer->sel.byte1, + buffer->cur_line, buffer->cur_index + ); + } else { + ledit_buffer_set_line_cursor_attrs( + buffer, buffer->cur_line, buffer->cur_index + ); + } + } + clear_key_stack(); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +enter_visual(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + ledit_buffer_set_mode(buffer, VISUAL); + buffer->sel.line1 = buffer->sel.line2 = buffer->cur_line; + buffer->sel.byte1 = buffer->sel.byte2 = buffer->cur_index; + ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + clear_key_stack(); /* FIXME: error if not empty? */ + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +switch_selection_end(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + swap(&buffer->sel.line1, &buffer->sel.line2); + swap(&buffer->sel.byte1, &buffer->sel.byte2); + buffer->cur_line = buffer->sel.line2; + buffer->cur_index = buffer->sel.byte2; + return (struct action){ACTION_NONE, NULL}; +} + +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 + +static struct action +enter_commandedit(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + ledit_window_set_bottom_bar_text(buffer->window, ":", -1); + ledit_window_set_bottom_bar_cursor(buffer->window, 1); + ledit_command_set_type(CMD_EDIT); + ledit_window_set_bottom_bar_text_shown(buffer->window, 1); + return (struct action){ACTION_GRABKEY, &ledit_command_key_handler}; +} + +static struct action +enter_searchedit_forward(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + ledit_window_set_bottom_bar_text(buffer->window, "/", -1); + ledit_window_set_bottom_bar_cursor(buffer->window, 1); + ledit_command_set_type(CMD_EDITSEARCH); + ledit_window_set_bottom_bar_text_shown(buffer->window, 1); + return (struct action){ACTION_GRABKEY, &ledit_command_key_handler}; +} + +static struct action +enter_searchedit_backward(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + ledit_window_set_bottom_bar_text(buffer->window, "?", -1); + ledit_window_set_bottom_bar_cursor(buffer->window, 1); + ledit_command_set_type(CMD_EDITSEARCHB); + ledit_window_set_bottom_bar_text_shown(buffer->window, 1); + return (struct action){ACTION_GRABKEY, &ledit_command_key_handler}; +} + +/* FIXME: support visual mode, i.e. change selection to new place? */ +static struct action +key_search_next(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + search_next(buffer); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +key_search_prev(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + search_prev(buffer); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +show_line(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + int textlen = snprintf(NULL, 0, "Line %d of %d", buffer->cur_line + 1, buffer->lines_num); + char *str = ledit_malloc(textlen + 1); + snprintf(str, textlen + 1, "Line %d of %d", buffer->cur_line + 1, buffer->lines_num); + ledit_window_show_message(buffer->window, str, textlen); + return (struct action){ACTION_NONE, NULL}; +} + +/* FIXME: return status! */ +static struct action +undo(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + ledit_buffer_set_selection(buffer, 0, 0, 0, 0); + ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + ledit_buffer_undo(buffer); + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +redo(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + ledit_buffer_set_selection(buffer, 0, 0, 0, 0); + ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + ledit_buffer_redo(buffer); + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +insert_mode_insert_text(ledit_buffer *buffer, char *text, int len) { + delete_selection(buffer); + insert_text(buffer, buffer->cur_line, buffer->cur_index, text, len, 1); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +clipcopy(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + /* FIXME: abstract this through buffer */ + clipboard_primary_to_clipboard(buffer->window); + return (struct action){ACTION_NONE, NULL}; +} + +static struct action +clippaste(ledit_buffer *buffer, char *text, int len) { + (void)text; + (void)len; + ledit_buffer_paste_clipboard(buffer); + return (struct action){ACTION_NONE, NULL}; +} + +struct action +basic_key_handler(ledit_buffer *buffer, 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; + unsigned int key_state = event->xkey.state; + preprocess_key(buffer->window, event, &sym, buf, sizeof(buf), &n); + + int found = 0; + struct key_stack_elem *e = peek_key_stack(); + /* FIXME: only hide when actually necessary */ + ledit_window_hide_message(buffer->window); + struct action act; + for (int i = 0; i < num_keys; i++) { + if (cur_keys[i].text) { + if (n > 0 && + (cur_keys[i].modes & buffer->common->mode) && + (!e || (e->key & cur_keys[i].prev_keys)) && + ((!strncmp(cur_keys[i].text, buf, n) && + match_key(cur_keys[i].mods, key_state & ~ShiftMask)) || + cur_keys[i].text[0] == '\0')) { + /* FIXME: seems a bit hacky to remove shift, but it + is needed to make keys that use shift match */ + act = cur_keys[i].func(buffer, buf, n); + found = 1; + break; + } + } else if ((cur_keys[i].modes & buffer->common->mode) && + cur_keys[i].keysym == sym && + match_key(cur_keys[i].mods, key_state)) { + act = cur_keys[i].func(buffer, buf, n); + found = 1; + break; + } + } + /* FIXME: only do this when necessary */ + if (found) { + ledit_buffer_ensure_cursor_shown(buffer); + return act; + } else { + /* FIXME: maybe show error */ + return (struct action){ACTION_NONE, NULL}; + } +} diff --git a/keys_basic.h b/keys_basic.h @@ -0,0 +1 @@ +struct action basic_key_handler(ledit_buffer *buffer, XEvent *event, int lang_index); diff --git a/keys_basic_config.h b/keys_basic_config.h @@ -0,0 +1,144 @@ +enum key_type { + KEY_NONE = 0, + KEY_MISC = 1, + KEY_CHAR = 2, + KEY_MOTION_CHAR = 4, + KEY_MOTION_LINE = 8, + KEY_MOTION = 4|8, + KEY_NUMBER = 16, + KEY_NUMBERALLOWED = 32, + KEY_ACTIONCALLBACK = 64, + 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 */ + enum ledit_mode modes; /* modes in which this keybinding is functional */ + enum key_type prev_keys; /* allowed previous keys */ + enum key_type key_types; /* key types - used to determine if the key is allowed */ + struct action (*func)(ledit_buffer *, char *, int); /* callback function */ +}; + +static struct action backspace(ledit_buffer *buffer, char *text, int len); +static struct action cursor_left(ledit_buffer *buffer, char *text, int len); +static struct action cursor_right(ledit_buffer *buffer, char *text, int len); +static struct action cursor_up(ledit_buffer *buffer, char *text, int len); +static struct action cursor_down(ledit_buffer *buffer, char *text, int len); +static struct action return_key(ledit_buffer *buffer, char *text, int len); +static struct action delete_key(ledit_buffer *buffer, char *text, int len); +static struct action escape_key(ledit_buffer *buffer, char *text, int len); +static struct action enter_insert(ledit_buffer *buffer, char *text, int len); +static struct action cursor_to_beginning(ledit_buffer *buffer, char *text, int len); +static struct action push_0(ledit_buffer *buffer, char *text, int len); +static struct action push_1(ledit_buffer *buffer, char *text, int len); +static struct action push_2(ledit_buffer *buffer, char *text, int len); +static struct action push_3(ledit_buffer *buffer, char *text, int len); +static struct action push_4(ledit_buffer *buffer, char *text, int len); +static struct action push_5(ledit_buffer *buffer, char *text, int len); +static struct action push_6(ledit_buffer *buffer, char *text, int len); +static struct action push_7(ledit_buffer *buffer, char *text, int len); +static struct action push_8(ledit_buffer *buffer, char *text, int len); +static struct action push_9(ledit_buffer *buffer, char *text, int len); +static struct action key_x(ledit_buffer *buffer, char *text, int len); +static struct action key_d(ledit_buffer *buffer, char *text, int len); +static struct action enter_visual(ledit_buffer *buffer, char *text, int len); +static struct action switch_selection_end(ledit_buffer *buffer, char *text, int len); +static struct action clipcopy(ledit_buffer *buffer, char *text, int len); +static struct action clippaste(ledit_buffer *buffer, char *text, int len); +static struct action show_line(ledit_buffer *buffer, char *text, int len); +static struct action enter_commandedit(ledit_buffer *buffer, char *text, int len); +static struct action enter_searchedit_backward(ledit_buffer *buffer, char *text, int len); +static struct action enter_searchedit_forward(ledit_buffer *buffer, char *text, int len); +static struct action key_search_next(ledit_buffer *buffer, char *text, int len); +static struct action key_search_prev(ledit_buffer *buffer, char *text, int len); +static struct action undo(ledit_buffer *buffer, char *text, int len); +static struct action redo(ledit_buffer *buffer, char *text, int len); +static struct action insert_mode_insert_text(ledit_buffer *buffer, char *text, int 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_ANY, &backspace}, + {NULL, 0, XK_Left, VISUAL|INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_left}, + {NULL, 0, XK_Right, VISUAL|INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_right}, + {NULL, 0, XK_Up, VISUAL|INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_up}, + {NULL, 0, XK_Down, VISUAL|INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_down}, + {NULL, 0, XK_Return, INSERT, KEY_ANY, KEY_ANY, &return_key}, + {NULL, 0, XK_Delete, INSERT, KEY_ANY, KEY_ANY, &delete_key}, + {NULL, 0, XK_Escape, VISUAL|INSERT, KEY_ANY, KEY_ANY, &escape_key}, + {"i", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_insert}, + {"h", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_left}, + {"l", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_right}, + {"j", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_down}, + {"k", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_up}, + {"0", 0, 0, NORMAL|VISUAL, ~KEY_NUMBER, KEY_ANY, &cursor_to_beginning}, + {"0", 0, 0, NORMAL|VISUAL, KEY_NUMBER, KEY_NUMBER, &push_0}, + {"1", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_1}, + {"2", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_2}, + {"3", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_3}, + {"4", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_4}, + {"5", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_5}, + {"6", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_6}, + {"7", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_7}, + {"8", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_8}, + {"9", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_9}, + {"x", 0, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &key_x}, + {"d", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION|KEY_NUMBERALLOWED, &key_d}, + {"v", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &enter_visual}, + {"o", 0, 0, VISUAL, KEY_ANY, KEY_ANY, &switch_selection_end}, + {"c", ControlMask, 0, INSERT|VISUAL, KEY_ANY, KEY_ANY, &clipcopy}, + {"v", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &clippaste}, + {"g", ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &show_line}, + {":", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_commandedit}, + {"?", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_searchedit_backward}, + {"/", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_searchedit_forward}, + {"n", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &key_search_next}, + {"N", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &key_search_prev}, + {"u", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &undo}, + {"U", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &redo}, + {"z", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &undo}, + {"y", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &redo}, + {"", 0, 0, INSERT, KEY_ANY, KEY_ANY, &insert_mode_insert_text} +}; + +static struct key keys_de[] = { +}; + +static struct key keys_ur[] = { + {NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ANY, &backspace}, + {NULL, 0, XK_Left, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_left}, + {NULL, 0, XK_Right, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_right}, + {NULL, 0, XK_Up, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_up}, + {NULL, 0, XK_Down, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_down}, + {NULL, 0, XK_Return, INSERT, KEY_ANY, KEY_ANY, &return_key}, + {NULL, 0, XK_Delete, INSERT, KEY_ANY, KEY_ANY, &delete_key}, + {NULL, 0, XK_Escape, INSERT, KEY_ANY, KEY_ANY, &escape_key}, + {"ی", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &enter_insert}, + {"ح", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_left}, + {"ل", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_right}, + {"ج", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_down}, + {"ک", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_up}, + {"0", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &cursor_to_beginning}, + {"چ", ControlMask, 0, INSERT|VISUAL, KEY_ANY, KEY_ANY, &clipcopy} +}; + +static struct key keys_hi[] = { + {NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ANY, &backspace}, + {NULL, 0, XK_Left, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_left}, + {NULL, 0, XK_Right, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_right}, + {NULL, 0, XK_Up, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_up}, + {NULL, 0, XK_Down, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_down}, + {NULL, 0, XK_Return, INSERT, KEY_ANY, KEY_ANY, &return_key}, + {NULL, 0, XK_Delete, INSERT, KEY_ANY, KEY_ANY, &delete_key}, + {NULL, 0, XK_Escape, INSERT, KEY_ANY, KEY_ANY, &escape_key}, + {"ि", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &enter_insert}, + {"ह", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_left}, + {"ल", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_right}, + {"ज", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_down}, + {"क", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_up}, + {"0", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &cursor_to_beginning} +}; + +GEN_KEY_ARRAY(struct key, keys_en, keys_de, keys_ur, keys_hi); diff --git a/keys_command.c b/keys_command.c @@ -0,0 +1,325 @@ +/* FIXME: Parse commands properly and allow combinations of commands */ +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <pango/pangoxft.h> +#include <X11/extensions/Xdbe.h> +#include <X11/keysym.h> +#include <X11/XF86keysym.h> +#include <X11/cursorfont.h> + +#include "memory.h" +#include "common.h" +#include "txtbuf.h" +#include "undo.h" +#include "cache.h" +#include "theme.h" +#include "window.h" +#include "buffer.h" +#include "search.h" + +#include "keys.h" +#include "action.h" +#include "keys_command.h" +#include "keys_command_config.h" + +/* FIXME: THIS WON'T WORK WHEN THERE ARE MULTIPLE BUFFERS! */ +/* this must first be set by caller before jumping to key handler */ +static enum ledit_command_type cur_type; + +void +ledit_command_set_type(enum ledit_command_type type) { + cur_type = type; +} + +static int handle_write(ledit_buffer *buffer, char *cmd, int l1, int l2); +static int handle_quit(ledit_buffer *buffer, char *cmd, int l1, int l2); +static int handle_write_quit(ledit_buffer *buffer, char *cmd, int l1, int l2); +static int handle_substitute(ledit_buffer *buffer, char *cmd, int l1, int l2); +static int parse_range(ledit_buffer *buffer, char *cmd, int len, char **cmd_ret, int *line1_ret, int *line2_ret); +static int handle_cmd(ledit_buffer *buffer, char *cmd, int len); + +static int +handle_write(ledit_buffer *buffer, char *cmd, int l1, int l2) { + (void)buffer; + (void)cmd; + (void)l1; + (void)l2; + /* FIXME: Implement properly; handle error */ + char *errstr; + if (buffer->filename) + ledit_buffer_write_to_file(buffer, buffer->filename, &errstr); + return 0; +} + +static int +handle_quit(ledit_buffer *buffer, char *cmd, int l1, int l2) { + (void)buffer; + (void)cmd; + (void)l1; + (void)l2; + /* FIXME: Implement */ + exit(1); + return 0; +} + +static int +handle_write_quit(ledit_buffer *buffer, char *cmd, int l1, int l2) { + (void)buffer; + (void)cmd; + (void)l1; + (void)l2; + printf("write quit\n"); + return 0; +} + +static int +handle_substitute(ledit_buffer *buffer, char *cmd, int l1, int l2) { + (void)buffer; + (void)cmd; + (void)l1; + (void)l2; + printf("substitute\n"); + return 0; +} + +enum cmd_type { + CMD_NORMAL, + CMD_OPTIONAL_RANGE +}; + +static const struct { + char *cmd; + enum cmd_type type; + int (*handler)(ledit_buffer *buffer, char *cmd, int l1, int l2); +} cmds[] = { + {"wq", CMD_OPTIONAL_RANGE, &handle_write_quit}, + {"w", CMD_OPTIONAL_RANGE, &handle_write}, + {"q", CMD_NORMAL, &handle_quit}, + {"s", CMD_OPTIONAL_RANGE, &handle_substitute} +}; + +/* +. current line - FIXME: implement +$ last line +% all lines +*/ + +/* FIXME: ACTUALLY USE LEN!!! */ +static int +parse_range(ledit_buffer *buffer, char *cmd, int len, char **cmd_ret, int *line1_ret, int *line2_ret) { + (void)len; + enum { + START_LINENO = 1, + START_RANGE = 2, + IN_RANGE = 4, + IN_LINENO = 8 + } s = START_LINENO | START_RANGE; + int l1 = -1, l2 = -1; + char *c = cmd; + for (; *c != '\0'; c++) { + if (isdigit(*c)) { + /* FIXME: integer overflow */ + if (s & IN_LINENO) { + if (l2 != -1) + l2 = l2 * 10 + (*c - '0'); + else + l1 = l1 * 10 + (*c - '0'); + } else if ((s & START_LINENO) && (s & START_RANGE)) { + l1 = *c - '0'; + s &= ~START_RANGE; + s &= ~START_LINENO; + s |= IN_RANGE | IN_LINENO; + } else if ((s & START_LINENO)) { + l2 = *c - '0'; + s &= ~START_LINENO; + s |= IN_LINENO; + } + } else if (*c == ',' && !(s & START_RANGE)) { + if (l1 != -1 && l2 != -1) { + return 1; + } else { + s |= START_LINENO; + s &= ~IN_LINENO; + } + } else if (*c == '%') { + if (s & START_RANGE) { + l1 = 1; + l2 = buffer->lines_num; + break; + } else { + return 1; + } + } else if (*c == '$') { + if (s & START_LINENO) { + if (l1 == -1) + l1 = buffer->lines_num; + else + l2 = buffer->lines_num; + s &= ~START_LINENO; + s &= ~IN_LINENO; + } else { + return 1; + } + } else { + break; + } + } + if ((l1 == -1 || l2 == -1) && !(s & START_RANGE)) + return 1; + *cmd_ret = c; + *line1_ret = l1; + *line2_ret = l2; + return 0; +} + +static int +handle_cmd(ledit_buffer *buffer, char *cmd, int len) { + if (len < 0) + len = strlen(cmd); + if (len < 1) + return 0; + char *c; + int l1, l2; + /* FIXME: show error msg here */ + if (parse_range(buffer, cmd, len, &c, &l1, &l2)) + return 0; + int range_given = l1 != -1 && l2 != -1; + 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(buffer, c, l1, l2); + } + } + return 0; +} + +static int +substitute_yes(ledit_buffer *buffer, char *key_text, int len) { + (void)buffer; + (void)key_text; + (void)len; + return 1; +} + +static int +substitute_yes_all(ledit_buffer *buffer, char *key_text, int len) { + (void)buffer; + (void)key_text; + (void)len; + return 0; +} + +static int +substitute_no(ledit_buffer *buffer, char *key_text, int len) { + (void)buffer; + (void)key_text; + (void)len; + return 1; +} + +static int +substitute_no_all(ledit_buffer *buffer, char *key_text, int len) { + (void)buffer; + (void)key_text; + (void)len; + return 0; +} + +static int +edit_insert_text(ledit_buffer *buffer, char *key_text, int len) { + ledit_window_insert_bottom_bar_text(buffer->window, key_text, len); + ledit_window_set_bottom_bar_cursor( + buffer->window, ledit_window_get_bottom_bar_cursor(buffer->window) + len + ); + return 1; +} + +static int +edit_submit(ledit_buffer *buffer, char *key_text, int len) { + (void)key_text; + (void)len; + ledit_buffer_set_mode(buffer, NORMAL); + ledit_window_set_bottom_bar_text_shown(buffer->window, 0); + /* FIXME: this is hacky */ + return handle_cmd(buffer, ledit_window_get_bottom_bar_text(buffer->window) + 1, -1); +} + +/* FIXME: support visual mode, i.e. change selection to new place? */ +void +search_next(ledit_buffer *buffer) { + /* FIXME: avoid this when line doesn't change */ + ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + enum ledit_search_state ret = ledit_search_next(buffer, &buffer->cur_line, &buffer->cur_index); + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + if (ret != SEARCH_NORMAL) + ledit_window_show_message(buffer->window, ledit_search_state_to_str(ret), -1); +} + +void +search_prev(ledit_buffer *buffer) { + /* FIXME: avoid this when line doesn't change */ + ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); + enum ledit_search_state ret = ledit_search_prev(buffer, &buffer->cur_line, &buffer->cur_index); + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); + if (ret != SEARCH_NORMAL) + ledit_window_show_message(buffer->window, ledit_search_state_to_str(ret), -1); +} + +static int +editsearch_submit(ledit_buffer *buffer, char *key_text, int len) { + (void)key_text; + (void)len; + ledit_buffer_set_mode(buffer, NORMAL); + ledit_window_set_bottom_bar_text_shown(buffer->window, 0); + ledit_set_search_forward(ledit_window_get_bottom_bar_text(buffer->window) + 1); + search_next(buffer); + return 0; +} + +static int +editsearchb_submit(ledit_buffer *buffer, char *key_text, int len) { + (void)key_text; + (void)len; + ledit_buffer_set_mode(buffer, NORMAL); + ledit_window_set_bottom_bar_text_shown(buffer->window, 0); + ledit_set_search_backward(ledit_window_get_bottom_bar_text(buffer->window) + 1); + search_next(buffer); + return 0; +} + +struct action +ledit_command_key_handler(ledit_buffer *buffer, 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; + unsigned int key_state = event->xkey.state; + preprocess_key(buffer->window, event, &sym, buf, sizeof(buf), &n); + int grabkey = 0; + for (int i = 0; i < num_keys; i++) { + if (cur_keys[i].text) { + if (n > 0 && + (cur_keys[i].type == cur_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(buffer, buf, n); + break; + } + } else if ((cur_keys[i].type == cur_type) && + (cur_keys[i].keysym == sym) && + (match_key(cur_keys[i].mods, key_state))) { + grabkey = cur_keys[i].func(buffer, buf, n); + break; + } + } + if (grabkey) + return (struct action){ACTION_GRABKEY, &ledit_command_key_handler}; + else + return (struct action){ACTION_NONE, NULL}; +} diff --git a/keys_command.h b/keys_command.h @@ -0,0 +1,15 @@ +enum ledit_command_type { + CMD_EDIT, + CMD_EDITSEARCH, + CMD_EDITSEARCHB, + CMD_SEARCH, + CMD_SEARCHB, + CMD_SUBSTITUTE +}; + +/* these are only here so they can also be used by keys_basic */ +void search_next(ledit_buffer *buffer); +void search_prev(ledit_buffer *buffer); + +void ledit_command_set_type(enum ledit_command_type type); +struct action ledit_command_key_handler(ledit_buffer *buffer, XEvent *event, int lang_index); diff --git a/keys_command_config.h b/keys_command_config.h @@ -0,0 +1,41 @@ +static int substitute_yes(ledit_buffer *buffer, char *key_text, int len); +static int substitute_yes_all(ledit_buffer *buffer, char *key_text, int len); +static int substitute_no(ledit_buffer *buffer, char *key_text, int len); +static int substitute_no_all(ledit_buffer *buffer, char *key_text, int len); +static int edit_insert_text(ledit_buffer *buffer, char *key_text, int len); +static int edit_submit(ledit_buffer *buffer, char *key_text, int len); +static int editsearch_submit(ledit_buffer *buffer, char *key_text, int len); +static int editsearchb_submit(ledit_buffer *buffer, char *key_text, int 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_buffer *, char *, int); /* 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, 0, XK_Return, CMD_EDIT, &edit_submit}, + {NULL, 0, XK_Return, CMD_EDITSEARCH, &editsearch_submit}, + {NULL, 0, XK_Return, CMD_EDITSEARCHB, &editsearchb_submit}, + {"", 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[] = { +}; + +static struct key keys_hi[] = { +}; + +static struct key keys_ur[] = { +}; + +GEN_KEY_ARRAY(struct key, keys_en, keys_de, keys_hi, keys_ur); diff --git a/lbuf.c b/lbuf.c @@ -1,51 +0,0 @@ -#include <stdlib.h> -#include <string.h> - -#include "memory.h" -#include "lbuf.h" - -lbuf * -lbuf_new(void) { - lbuf *buf = ledit_malloc(sizeof(lbuf)); - buf->text = NULL; - buf->cap = buf->len = 0; - return buf; -} - -void -lbuf_grow(lbuf *buf, size_t sz) { - /* always leave room for extra \0 */ - if (sz + 1 > buf->cap) { - /* FIXME: what are the best values here? */ - buf->cap = buf->cap * 2 > sz + 1 ? buf->cap * 2 : sz + 1; - buf->text = ledit_realloc(buf->text, buf->cap); - } -} - -void -lbuf_shrink(lbuf *buf) { - if ((buf->len + 1) * 4 < buf->cap) { - buf->cap /= 2; - buf->text = ledit_realloc(buf->text, buf->cap); - } -} - -void -lbuf_destroy(lbuf *buf) { - free(buf->text); - free(buf); -} - -void -lbuf_cpy(lbuf *dst, lbuf *src) { - lbuf_grow(dst, src->len); - memcpy(dst->text, src->text, src->len); - dst->len = src->len; -} - -lbuf * -lbuf_dup(lbuf *src) { - lbuf *dst = lbuf_new(); - lbuf_cpy(dst, src); - return dst; -} diff --git a/lbuf.h b/lbuf.h @@ -1,12 +0,0 @@ -/* FIXME: RENAME THIS */ -typedef struct { - size_t len, cap; - char *text; -} lbuf; - -lbuf *lbuf_new(void); -void lbuf_grow(lbuf *buf, size_t sz); -void lbuf_shrink(lbuf *buf); -void lbuf_destroy(lbuf *buf); -void lbuf_cpy(lbuf *dst, lbuf *src); -lbuf *lbuf_dup(lbuf *src); diff --git a/ledit.c b/ledit.c @@ -1,3 +1,4 @@ +/* FIXME: Document that everything is assumed to be utf8 */ /* FIXME: Only redraw part of screen if needed */ /* FIXME: overflow in repeated commands */ /* FIXME: Fix lag when scrolling - combine repeated mouse motion events */ @@ -16,103 +17,30 @@ #include <limits.h> #include <unistd.h> #include <locale.h> + #include <X11/Xlib.h> #include <X11/Xatom.h> #include <X11/Xutil.h> #include <X11/keysym.h> #include <X11/XF86keysym.h> #include <X11/cursorfont.h> - #include <pango/pangoxft.h> - #include <X11/XKBlib.h> #include <X11/extensions/XKBrules.h> #include <X11/extensions/Xdbe.h> #include "memory.h" #include "common.h" -#include "lbuf.h" -#include "buffer.h" -#include "search.h" +#include "txtbuf.h" +#include "theme.h" +#include "window.h" #include "cache.h" -#include "util.h" #include "undo.h" -#include "commands.h" - -enum key_type { - KEY_NONE = 0, - KEY_MISC = 1, - KEY_CHAR = 2, - KEY_MOTION_CHAR = 4, - KEY_MOTION_LINE = 8, - KEY_MOTION = 4|8, - KEY_NUMBER = 16, - KEY_NUMBERALLOWED = 32, - KEY_ANY = 0xFF -}; - -struct { - /* FIXME: encapsulate layout, width, draw a bit */ - PangoLayout *mode; - ledit_draw *mode_draw; - int mode_w, mode_h; - PangoLayout *ruler; - ledit_draw *ruler_draw; - int ruler_w, ruler_h; - PangoLayout *line; - ledit_draw *line_draw; - int line_w, line_h; - char *line_text; - int line_alloc, line_len; - int line_cur_pos; -} bottom_bar; - -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_mode modes; /* modes in which this keybinding is functional */ - enum key_type prev_keys; /* allowed previous keys */ - enum key_type key_types; /* key types - used to determine if the key is allowed */ - void (*func)(void); /* callback function */ -}; - -struct key_stack_elem { - enum key_type key; - enum key_type followup; /* allowed keys to complete the keybinding */ - /* callback function that motion commands call to complete a command - - * line and char_pos already include the repetition stored in this stack - * element; type is the type of motion command (used to determine if - * the command should operate on lines or chars) */ - void (*motion_cb)(int line, int char_pos, enum key_type type); - int count; /* number of repetitions */ - int data1; /* misc. data 1 */ - int data2; /* misc. data 2 */ -}; - -/* buffer for storing yanked text */ -lbuf *paste_buffer = NULL; -/* temporary buffer used for storing text - in order to add it to the undo stack */ -lbuf *tmp_buffer = NULL; - -static struct { - size_t len, alloc; - struct key_stack_elem *stack; -} key_stack; - -static ledit_common_state state; -static ledit_buffer *buffer; - -/* TODO: protect against overflow, especially on repeating commands */ - -static struct key_stack_elem *push_key_stack(void); -static struct key_stack_elem *peek_key_stack(void); -static struct key_stack_elem *pop_key_stack(void); -void clear_key_stack(void); +#include "buffer.h" +#include "action.h" +#include "keys.h" +#include "keys_basic.h" -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[]); @@ -121,649 +49,23 @@ static void redraw(void); 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 backspace(void); -static void delete_key(void); -static void move_cursor_left_right(int dir); -static void cursor_left(void); -static void cursor_right(void); -static void move_cursor_up_down(int dir); -static void cursor_down(void); -static void cursor_up(void); -static void cursor_to_beginning(void); -static void return_key(void); -static void escape_key(void); -static void enter_insert(void); -static void key_x(void); -static void push_num(int num); -static void push_0(void); -static void push_1(void); -static void push_2(void); -static void push_3(void); -static void push_4(void); -static void push_5(void); -static void push_6(void); -static void push_7(void); -static void push_8(void); -static void push_9(void); -static void key_d(void); -static void key_d_cb(int line, int char_pos, enum key_type type); static void change_keyboard(char *lang); -static void key_press(XEvent event); -static void get_new_line_softline( - int cur_line, int cur_index, int movement, - int *new_line_ret, int *new_softline_ret -); - -#define SCROLLBAR_WIDTH 10 -#define SCROLL_STEP 10 - -/* FIXME: shouldn't state.bottom_text_shown also be true when message_shown? */ -static void -recalc_text_size(void) { - int bar_h = bottom_bar.mode_h; - if ((state.bottom_text_shown || state.message_shown) && bottom_bar.line_h > bar_h) - bar_h = bottom_bar.line_h; - state.text_w = state.w - SCROLLBAR_WIDTH; - state.text_h = state.h - bar_h; -} - -/* FIXME: allow lines longer than window width to be displayed properly */ -static void -insert_bottom_bar_text(char *text, int len) { - assert(len >= -1); - assert(bottom_bar.line_cur_pos <= bottom_bar.line_alloc); - - if (len == -1) - len = strlen(text); - /* \0 not included in len */ - if (bottom_bar.line_len + len + 1 > bottom_bar.line_alloc || bottom_bar.line_text == NULL) { - /* FIXME: read up on what the best values are here */ - bottom_bar.line_alloc = - bottom_bar.line_alloc * 2 > bottom_bar.line_len + len + 1 ? - bottom_bar.line_alloc * 2 : - bottom_bar.line_len + len + 1; - bottom_bar.line_text = ledit_realloc(bottom_bar.line_text, bottom_bar.line_alloc); - } - memmove( - bottom_bar.line_text + bottom_bar.line_cur_pos + len, - bottom_bar.line_text + bottom_bar.line_cur_pos, - bottom_bar.line_len - bottom_bar.line_cur_pos - ); - memcpy(bottom_bar.line_text + bottom_bar.line_cur_pos, text, len); - bottom_bar.line_len += len; - bottom_bar.line_text[bottom_bar.line_len] = '\0'; - pango_layout_set_text(bottom_bar.line, bottom_bar.line_text, bottom_bar.line_len); - pango_layout_get_pixel_size(bottom_bar.line, &bottom_bar.line_w, &bottom_bar.line_h); - ledit_grow_draw(&state, bottom_bar.line_draw, bottom_bar.line_w, bottom_bar.line_h); - XftDrawRect(bottom_bar.line_draw->xftdraw, &state.bg, 0, 0, bottom_bar.line_w, bottom_bar.line_h); - pango_xft_render_layout(bottom_bar.line_draw->xftdraw, &state.fg, bottom_bar.line, 0, 0); - recalc_text_size(); -} - -static void -set_bottom_bar_text(char *text, int len) { - bottom_bar.line_len = 0; - bottom_bar.line_cur_pos = 0; - insert_bottom_bar_text(text, len); -} - -static void -show_message(char *text, int len) { - set_bottom_bar_text(text, len); - /* FIXME: rename these */ - state.bottom_text_shown = 0; - state.message_shown = 2; -} - -/* clipboard handling largely stolen from st (simple terminal) */ - -#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) +static void key_press(XEvent *event); -struct { - Atom xtarget; - lbuf *primary; - char *clipboard; -} xsel; - -static void -set_mode(enum ledit_mode mode) { - state.mode = mode; - switch (mode) { - case NORMAL: - pango_layout_set_text(bottom_bar.mode, "Normal", -1); - break; - case VISUAL: - pango_layout_set_text(bottom_bar.mode, "Visual", -1); - break; - case INSERT: - pango_layout_set_text(bottom_bar.mode, "Insert", -1); - break; - default: - pango_layout_set_text(bottom_bar.mode, "ledit is buggy", -1); - break; - } - pango_layout_get_pixel_size(bottom_bar.mode, &bottom_bar.mode_w, &bottom_bar.mode_h); - ledit_grow_draw(&state, bottom_bar.mode_draw, bottom_bar.mode_w, bottom_bar.mode_h); - XftDrawRect(bottom_bar.mode_draw->xftdraw, &state.bg, 0, 0, bottom_bar.mode_w, bottom_bar.mode_h); - pango_xft_render_layout(bottom_bar.mode_draw->xftdraw, &state.fg, bottom_bar.mode, 0, 0); - recalc_text_size(); - ledit_change_mode_group(buffer); -} - -void -clipcopy(void) -{ - Atom clipboard; - - free(xsel.clipboard); - xsel.clipboard = NULL; - - /* FIXME: don't copy if text empty (no selection)? */ - if (xsel.primary->text != NULL) { - xsel.clipboard = ledit_strdup(xsel.primary->text); - clipboard = XInternAtom(state.dpy, "CLIPBOARD", 0); - XSetSelectionOwner(state.dpy, clipboard, state.win, CurrentTime); - } -} - -void -clippaste(void) -{ - Atom clipboard; - - clipboard = XInternAtom(state.dpy, "CLIPBOARD", 0); - XConvertSelection(state.dpy, clipboard, xsel.xtarget, clipboard, - state.win, CurrentTime); -} - -void -selpaste(void) -{ - XConvertSelection(state.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, - state.win, CurrentTime); -} - -void selnotify(XEvent *e); - -void -propnotify(XEvent *e) -{ - XPropertyEvent *xpev; - Atom clipboard = XInternAtom(state.dpy, "CLIPBOARD", 0); - - xpev = &e->xproperty; - if (xpev->state == PropertyNewValue && - (xpev->atom == XA_PRIMARY || - xpev->atom == clipboard)) { - selnotify(e); - } -} - -void -selnotify(XEvent *e) -{ - unsigned long nitems, ofs, rem; - int format; - unsigned char *data; - Atom type, incratom, property = None; - - incratom = XInternAtom(state.dpy, "INCR", 0); - - ofs = 0; - if (e->type == SelectionNotify) { - property = e->xselection.property; - } else if (e->type == PropertyNotify) { - property = e->xproperty.atom; - } - - if (property == None) - return; - - do { - if (XGetWindowProperty(state.dpy, state.win, property, ofs, - BUFSIZ/4, False, AnyPropertyType, - &type, &format, &nitems, &rem, - &data)) { - fprintf(stderr, "Clipboard allocation failed\n"); - return; - } - - if (e->type == PropertyNotify && nitems == 0 && rem == 0) { - /* - * If there is some PropertyNotify with no data, then - * this is the signal of the selection owner that all - * data has been transferred. We won't need to receive - * PropertyNotify events anymore. - */ - MODBIT(state.wattrs.event_mask, 0, PropertyChangeMask); - XChangeWindowAttributes(state.dpy, state.win, CWEventMask, &state.wattrs); - } - - if (type == incratom) { - /* - * Activate the PropertyNotify events so we receive - * when the selection owner sends us the next - * chunk of data. - */ - MODBIT(state.wattrs.event_mask, 1, PropertyChangeMask); - XChangeWindowAttributes(state.dpy, state.win, CWEventMask, &state.wattrs); - - /* - * Deleting the property is the transfer start signal. - */ - XDeleteProperty(state.dpy, state.win, (int)property); - continue; - } - - ledit_insert_text_with_newlines( - buffer, - buffer->cur_line, buffer->cur_index, - (char*)data, (int)(nitems * format / 8), - &buffer->cur_line, &buffer->cur_index - ); - XFree(data); - /* number of 32-bit chunks returned */ - ofs += nitems * format / 32; - } while (rem > 0); - - /* - * Deleting the property again tells the selection owner to send the - * next data chunk in the property. - */ - XDeleteProperty(state.dpy, state.win, (int)property); -} - -void -selrequest(XEvent *e) -{ - XSelectionRequestEvent *xsre; - XSelectionEvent xev; - Atom xa_targets, string, clipboard; - char *seltext; - - xsre = (XSelectionRequestEvent *) e; - xev.type = SelectionNotify; - xev.requestor = xsre->requestor; - xev.selection = xsre->selection; - xev.target = xsre->target; - xev.time = xsre->time; - if (xsre->property == None) - xsre->property = xsre->target; - - /* reject */ - xev.property = None; - - xa_targets = XInternAtom(state.dpy, "TARGETS", 0); - if (xsre->target == xa_targets) { - /* respond with the supported type */ - string = xsel.xtarget; - XChangeProperty(xsre->display, xsre->requestor, xsre->property, - XA_ATOM, 32, PropModeReplace, - (unsigned char *) &string, 1); - xev.property = xsre->property; - } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { - /* - * xith XA_STRING non ascii characters may be incorrect in the - * requestor. It is not our problem, use utf8. - */ - clipboard = XInternAtom(state.dpy, "CLIPBOARD", 0); - if (xsre->selection == XA_PRIMARY) { - seltext = xsel.primary->text; - } else if (xsre->selection == clipboard) { - seltext = xsel.clipboard; - } else { - fprintf(stderr, - "Unhandled clipboard selection 0x%lx\n", - xsre->selection); - return; - } - if (seltext != NULL) { - XChangeProperty(xsre->display, xsre->requestor, - xsre->property, xsre->target, - 8, PropModeReplace, - (unsigned char *)seltext, strlen(seltext)); - xev.property = xsre->property; - } - } - - /* all done, send a notification to the listener */ - if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) - fprintf(stderr, "Error sending SelectionNotify event\n"); -} - -static void -get_new_line_softline( - int cur_line, int cur_index, int movement, - int *new_line_ret, int *new_softline_ret) { - ledit_line *line = ledit_get_line(buffer, cur_line); - int x, softline; - pango_layout_index_to_line_x(line->layout, cur_index, 0, &softline, &x); - if (movement > 0) { - int softlines = pango_layout_get_line_count(line->layout); - if (softlines - softline > movement) { - *new_line_ret = cur_line; - *new_softline_ret = softline + movement; - } else { - movement -= (softlines - softline - 1); - int endline = cur_line + 1; - while (movement > 0 && endline < buffer->lines_num) { - line = ledit_get_line(buffer, endline); - softlines = pango_layout_get_line_count(line->layout); - movement -= softlines; - endline++; - } - endline--; - if (movement <= 0) { - *new_softline_ret = movement + softlines - 1; - } else { - *new_softline_ret = softlines - 1; - } - *new_line_ret = endline; - } - } else if (movement < 0) { - int softlines = 0; - if (softline + movement >= 0) { - *new_line_ret = cur_line; - *new_softline_ret = softline + movement; - } else { - movement += softline; - int endline = cur_line - 1; - while (movement < 0 && endline >= 0) { - line = ledit_get_line(buffer, endline); - softlines = pango_layout_get_line_count(line->layout); - movement += softlines; - endline--; - } - endline++; - if (movement >= 0) { - *new_softline_ret = movement; - } else { - *new_softline_ret = 0; - } - *new_line_ret = endline; - } - } else { - *new_line_ret = cur_line; - *new_softline_ret = softline; - } -} - -/* FIXME: don't overwrite buffer->cur_line, etc. here? */ -static void -delete_range( - int line_based, int selected, - int line_index1, int byte_index1, - int line_index2, int byte_index2) { - (void)selected; /* FIXME */ - ledit_range cur_range, del_range; - cur_range.line1 = buffer->cur_line; - cur_range.byte1 = buffer->cur_index; - ledit_delete_range( - buffer, line_based, - line_index1, byte_index1, - line_index2, byte_index2, - &buffer->cur_line, &buffer->cur_index, - &del_range, paste_buffer - ); - cur_range.line2 = buffer->cur_line; - cur_range.byte2 = buffer->cur_index; - ledit_push_undo_delete( - buffer, paste_buffer, del_range, cur_range, 1 - ); -} - -static void -insert_text(int line, int index, char *text, int len, int start_group) { - if (len < 0) - len = strlen(text); - /* FIXME: this is kind of hacky... */ - lbuf ins_buf = {.text = text, .len = len, .cap = len}; - ledit_range cur_range, del_range; - cur_range.line1 = buffer->cur_line; - cur_range.byte1 = buffer->cur_index; - del_range.line1 = line; - del_range.byte1 = index; - ledit_insert_text_with_newlines( - buffer, line, index, text, len, - &buffer->cur_line, &buffer->cur_index - ); - cur_range.line2 = buffer->cur_line; - cur_range.byte2 = buffer->cur_index; - del_range.line2 = buffer->cur_line; - del_range.byte2 = buffer->cur_index; - ledit_push_undo_insert( - buffer, &ins_buf, del_range, cur_range, start_group - ); -} - -static int -delete_selection(void) { - if (buffer->sel.line1 != buffer->sel.line2 || buffer->sel.byte1 != buffer->sel.byte2) { - delete_range( - 0, 0, - buffer->sel.line1, buffer->sel.byte1, - buffer->sel.line2, buffer->sel.byte2 - ); - /* FIXME: maybe just set this to the current cursor pos? */ - buffer->sel.line1 = buffer->sel.line2 = -1; - buffer->sel.byte1 = buffer->sel.byte2 = -1; - ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); - return 1; - } - return 0; -} - -static void -key_d(void) { - int num = 0; - if (delete_selection()) { - set_mode(NORMAL); - buffer->cur_index = ledit_get_legal_normal_pos(buffer, buffer->cur_line, buffer->cur_index); - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); - clear_key_stack(); - } else { - struct key_stack_elem *e = pop_key_stack(); - if (e != NULL) { - if (e->key & KEY_NUMBER) { - num = e->count; - e = pop_key_stack(); - } - /* FIXME: checking equality of the function pointer may be a bit risky */ - if (e != NULL && e->motion_cb == &key_d_cb) { - int prevnum = e->count > 0 ? e->count : 1; - num = num > 0 ? num : 1; - int lines = num * prevnum; - int new_line, new_softline; - get_new_line_softline( - buffer->cur_line, buffer->cur_index, lines - 1, - &new_line, &new_softline - ); - ledit_line *ll = ledit_get_line(buffer, new_line); - PangoLayoutLine *pl = pango_layout_get_line_readonly(ll->layout, new_softline); - e->motion_cb(new_line, pl->start_index, KEY_MOTION_LINE); - clear_key_stack(); - } else if (e != NULL) { - clear_key_stack(); - } - } - if (e == NULL) { - e = push_key_stack(); - e->key = KEY_MOTION; /* ? */ - e->count = num; - e->motion_cb = &key_d_cb; - } - } -} - -/* FIXME: should this get number of lines to remove or actual end line? */ -static void -key_d_cb(int line, int char_pos, enum key_type type) { - int line_based = type == KEY_MOTION_LINE ? 1 : 0; - delete_range( - line_based, 0, - buffer->cur_line, buffer->cur_index, - line, char_pos - ); - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); -} - -static void -key_x(void) { - struct key_stack_elem *e = pop_key_stack(); - int num = 1; - clear_key_stack(); - if (e && !(e->key & KEY_NUMBER)) - return; - if (e) - num = e->count; - if (num <= 0) - num = 1; - /* FIXME: actually do something */ -} - -static void -push_num(int num) { - struct key_stack_elem *e = peek_key_stack(); - if (!e || !(e->key & KEY_NUMBER)) { - e = push_key_stack(); - e->key = KEY_NUMBER; - e->followup = KEY_NUMBER|KEY_NUMBERALLOWED; - } - /* FIXME: error (overflow) checking */ - e->count *= 10; - e->count += num; -} - -static void -push_0(void) { - push_num(0); -} - -static void -push_1(void) { - push_num(1); -} - -static void -push_2(void) { - push_num(2); -} - -static void -push_3(void) { - push_num(3); -} - -static void -push_4(void) { - push_num(4); -} - -static void -push_5(void) { - push_num(5); -} - -static void -push_6(void) { - push_num(6); -} - -static void -push_7(void) { - push_num(7); -} - -static void -push_8(void) { - push_num(8); -} - -static void -push_9(void) { - push_num(9); -} - -int -main(int argc, char *argv[]) { - setup(argc, argv); - mainloop(); - cleanup(); - - return 0; -} - -static struct key_stack_elem * -push_key_stack(void) { - struct key_stack_elem *e; - if (key_stack.len >= key_stack.alloc) { - size_t new_alloc = key_stack.alloc > 0 ? key_stack.alloc * 2 : 4; - key_stack.stack = ledit_realloc( - key_stack.stack, new_alloc * sizeof(struct key_stack_elem) - ); - key_stack.alloc = new_alloc; - } - e = &key_stack.stack[key_stack.len]; - e->key = KEY_NONE; - e->followup = KEY_NONE; - e->motion_cb = NULL; - e->count = 0; - e->data1 = 0; - e->data2 = 0; - key_stack.len++; - return &key_stack.stack[key_stack.len - 1]; -} - -/* Note: for peek and pop, the returned element is only valid - * until the next element is pushed */ -/* Note on the note: that's not entirely true for peek */ -static struct key_stack_elem * -peek_key_stack(void) { - if (key_stack.len > 0) - return &key_stack.stack[key_stack.len - 1]; - return NULL; -} - -static struct key_stack_elem * -pop_key_stack(void) { - if (key_stack.len > 0) { - key_stack.len--; - return &key_stack.stack[key_stack.len]; - } - return NULL; -} - -void -clear_key_stack(void) { - key_stack.len = 0; -} - -static void -get_scroll_pos_height(double *pos, double *height) { - *height = ((double)state.text_h / buffer->total_height) * state.text_h; - *pos = (buffer->display_offset / - (buffer->total_height - state.text_h)) * (state.text_h - *height); -} - -static void -set_scroll_pos(double pos) { - buffer->display_offset = pos * (buffer->total_height / (double)state.text_h); - if (buffer->display_offset < 0) - buffer->display_offset = 0; - if (buffer->display_offset + state.text_h > buffer->total_height) - buffer->display_offset = buffer->total_height - state.text_h; -} +ledit_theme *theme = NULL; +ledit_window *window = NULL; +ledit_buffer *buffer = NULL; +ledit_common common; +int cur_lang = 0; +struct action cur_action = {ACTION_NONE, NULL}; static void mainloop(void) { XEvent event; int xkb_event_type; int major, minor; - if (!XkbQueryExtension(state.dpy, 0, &xkb_event_type, NULL, &major, &minor)) { + if (!XkbQueryExtension(common.dpy, 0, &xkb_event_type, NULL, &major, &minor)) { fprintf(stderr, "XKB not supported."); exit(1); } @@ -774,14 +76,14 @@ mainloop(void) { * 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, + common.dpy, XkbUseCoreKbd, XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask ); XkbSelectEventDetails( - state.dpy, XkbUseCoreKbd, XkbStateNotify, + common.dpy, XkbUseCoreKbd, XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask ); - XSync(state.dpy, False); + XSync(common.dpy, False); int running = 1; int change_kbd = 0; @@ -790,7 +92,7 @@ mainloop(void) { while (running) { do { - XNextEvent(state.dpy, &event); + XNextEvent(common.dpy, &event); if (event.type == xkb_event_type) { change_kbd = 1; continue; @@ -799,6 +101,7 @@ mainloop(void) { continue; switch (event.type) { case Expose: + /* FIXME: why did I do this? */ redraw(); need_redraw = 1; break; @@ -820,37 +123,34 @@ mainloop(void) { break; case KeyPress: need_redraw = 1; - key_press(event); + key_press(&event); break; case ClientMessage: - if ((Atom)event.xclient.data.l[0] == state.wm_delete_msg) + if ((Atom)event.xclient.data.l[0] == buffer->window->wm_delete_msg) running = 0; break; case SelectionNotify: - need_redraw = 1; - selnotify(&event); - break; case PropertyNotify: + /* redraw because text may be pasted, then fall + through and let window handle the event */ need_redraw = 1; - propnotify(&event); - break; case SelectionRequest: - selrequest(&event); + ledit_window_clipboard_event(buffer->window, &event); break; default: break; } - } while (XPending(state.dpy)); + } while (XPending(common.dpy)); if (change_kbd) { change_kbd = 0; XkbStateRec s; - XkbGetState(state.dpy, XkbUseCoreKbd, &s); + XkbGetState(common.dpy, XkbUseCoreKbd, &s); XkbDescPtr desc = XkbGetKeyboard( - state.dpy, XkbAllComponentsMask, XkbUseCoreKbd + common.dpy, XkbAllComponentsMask, XkbUseCoreKbd ); char *group = XGetAtomName( - state.dpy, desc->names->groups[s.group] + common.dpy, desc->names->groups[s.group] ); change_keyboard(group); XFree(group); @@ -867,30 +167,16 @@ static void setup(int argc, char *argv[]) { setlocale(LC_CTYPE, ""); XSetLocaleModifiers(""); - XSetWindowAttributes attrs; - XGCValues gcv; - - state.scroll_dragging = 0; - state.scroll_grab_handle = 0; - state.selecting = 0; - state.w = 500; - state.h = 500; - state.dpy = XOpenDisplay(NULL); - state.screen = DefaultScreen(state.dpy); + common.dpy = XOpenDisplay(NULL); + common.screen = DefaultScreen(common.dpy); /* 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 - ); - */ + if (XdbeQueryExtension(common.dpy, &major, &minor)) { int num_screens = 1; - Drawable screens[] = { DefaultRootWindow(state.dpy) }; + Drawable screens[] = {DefaultRootWindow(common.dpy)}; XdbeScreenVisualInfo *info = XdbeGetVisualInfo( - state.dpy, screens, &num_screens + common.dpy, screens, &num_screens ); if (!info || num_screens < 1 || info->count < 1) { fprintf(stderr, "No visuals support Xdbe.\n"); @@ -903,7 +189,7 @@ setup(int argc, char *argv[]) { xvisinfo_templ.depth = info->visinfo[0].depth; int matches; XVisualInfo *xvisinfo_match = XGetVisualInfo( - state.dpy, + common.dpy, VisualIDMask | VisualScreenMask | VisualDepthMask, &xvisinfo_templ, &matches ); @@ -914,1075 +200,97 @@ setup(int argc, char *argv[]) { ); exit(1); } - state.vis = xvisinfo_match->visual; + common.vis = xvisinfo_match->visual; } else { fprintf(stderr, "No Xdbe support.\n"); exit(1); } - state.depth = DefaultDepth(state.dpy, state.screen); - state.cm = DefaultColormap(state.dpy, state.screen); - - memset(&state.wattrs, 0, sizeof(attrs)); - state.wattrs.background_pixel = BlackPixel(state.dpy, state.screen); - state.wattrs.colormap = state.cm; - /* this causes the window contents to be kept - * when it is resized, leading to less flicker */ - state.wattrs.bit_gravity = NorthWestGravity; - /* FIXME: FocusChangeMask? */ - state.wattrs.event_mask = KeyPressMask | - ExposureMask | VisibilityChangeMask | StructureNotifyMask | - ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; - state.win = XCreateWindow( - state.dpy, DefaultRootWindow(state.dpy), 0, 0, - state.w, state.h, 0, state.depth, - InputOutput, state.vis, - CWBackPixel | CWColormap | CWBitGravity | CWEventMask, &state.wattrs - ); - XSetStandardProperties(state.dpy, state.win, "ledit", NULL, None, argv, argc, NULL); - - 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); - - 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); - - /* blatantly stolen from st (simple terminal) */ - /* FIXME: get improved input handling from newer version of st */ - if ((state.xim = XOpenIM(state.dpy, NULL, NULL, NULL)) == NULL) { - XSetLocaleModifiers("@im=local"); - 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" - ); - exit(1); - } - } - } - 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" - ); - exit(1); - } - XSetICFocus(state.xic); - - bottom_bar.mode = pango_layout_new(state.context); - pango_layout_set_font_description(bottom_bar.mode, state.font); - /* FIXME: only create "dummy draw" at first and create with proper size when needed */ - bottom_bar.mode_draw = ledit_create_draw(&state, 10, 10); - bottom_bar.line = pango_layout_new(state.context); - pango_layout_set_font_description(bottom_bar.line, state.font); - bottom_bar.line_draw = ledit_create_draw(&state, 10, 10); - bottom_bar.line_w = bottom_bar.line_h = 10; - bottom_bar.line_text = NULL; - bottom_bar.line_alloc = bottom_bar.line_len = 0; - bottom_bar.line_cur_pos = 0; - state.bottom_text_shown = 0; - state.message_shown = 0; - - XMapWindow(state.dpy, state.win); + common.depth = DefaultDepth(common.dpy, common.screen); + common.cm = DefaultColormap(common.dpy, common.screen); - ledit_init_cache(&state); - buffer = ledit_create_buffer(&state); - /* FIXME: move this to create_buffer */ - ledit_init_undo_stack(buffer); + theme = ledit_theme_create(&common); + window = ledit_window_create(&common, theme); + buffer = ledit_buffer_create(&common, theme, window); /* FIXME: Support multiple buffers/files */ if (argc > 1) { char *load_err; - if (ledit_load_file_into_buffer(buffer, argv[1], 0, &load_err)) { + if (ledit_buffer_load_file(buffer, argv[1], 0, &load_err)) { fprintf(stderr, "Error opening file '%s': %s\n", argv[1], load_err); exit(1); } + /* FIXME: encapsulate */ buffer->filename = ledit_strdup(argv[1]); } - set_mode(NORMAL); - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); - - key_stack.len = key_stack.alloc = 0; - key_stack.stack = NULL; + ledit_buffer_set_mode(buffer, NORMAL); + ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); - paste_buffer = lbuf_new(); - tmp_buffer = lbuf_new(); - - xsel.primary = lbuf_new(); - xsel.clipboard = NULL; - xsel.xtarget = XInternAtom(state.dpy, "UTF8_STRING", 0); - if (xsel.xtarget == None) - xsel.xtarget = XA_STRING; - - recalc_text_size(); redraw(); } static void cleanup(void) { /* FIXME: cleanup everything else */ + /* ledit_cleanup_search(); ledit_destroy_cache(); ledit_destroy_undo_stack(buffer); ledit_destroy_buffer(buffer); - XDestroyWindow(state.dpy, state.win); - XCloseDisplay(state.dpy); -} - -static void -redraw(void) { - 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.text_h) { - final_h -= final_y + final_h - - buffer->display_offset - state.text_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.text_h) - break; - h += line->h; - } - - 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 || state.mode == VISUAL) { - 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.text_h) { - XSetForeground(state.dpy, state.gc, state.scroll_bg.pixel); - XFillRectangle( - state.dpy, state.drawable, state.gc, - state.w - SCROLLBAR_WIDTH, 0, SCROLLBAR_WIDTH, state.text_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 - SCROLLBAR_WIDTH, (int)round(scroll_y), - SCROLLBAR_WIDTH, (int)round(scroll_h) - ); - } - XSetForeground(state.dpy, state.gc, state.bg.pixel); - /* FIXME: allow different color for bar */ - XFillRectangle( - state.dpy, state.drawable, state.gc, - 0, state.text_h, - state.w, state.h - state.text_h - ); - if (state.bottom_text_shown || state.message_shown) { - XCopyArea( - state.dpy, bottom_bar.line_draw->pixmap, - state.drawable, state.gc, - 0, 0, bottom_bar.line_w, bottom_bar.line_h, - 0, state.text_h - ); - } else { - XCopyArea( - state.dpy, bottom_bar.mode_draw->pixmap, - state.drawable, state.gc, - 0, 0, bottom_bar.mode_w, bottom_bar.mode_h, - state.w - bottom_bar.mode_w, state.text_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); -} - -static void -xy_to_line_byte(int x, int y, int *line_ret, int *byte_ret) { - /* FIXME: store current line offset to speed this up */ - /* FIXME: use y_offset in lines */ - long h = 0; - double pos = buffer->display_offset + y; - for (int i = 0; i < buffer->lines_num; i++) { - ledit_line *line = ledit_get_line(buffer, i); - if ((h <= pos && h + line->h > pos) || i == buffer->lines_num - 1) { - int index, trailing; - pango_layout_xy_to_index( - line->layout, - x * PANGO_SCALE, (int)(pos - h) * PANGO_SCALE, - &index, &trailing - ); - while (trailing > 0) { - trailing--; - index = ledit_next_utf8(line, index); - } - *line_ret = i; - *byte_ret = index; - break; - } - h += line->h; - } -} - -static void -swap(int *a, int *b) { - int tmp = *a; - *a = *b; - *b = tmp; -} - -static void -sort_selection(int *line1, int *byte1, int *line2, int *byte2) { - if (*line1 > *line2) { - swap(line1, line2); - swap(byte1, byte2); - } else if (*line1 == *line2 && *byte1 > *byte2) { - swap(byte1, byte2); - } -} - -/* FIXME: don't reset selection when selection is clicked away */ -/* FIXME: when selecting with mouse, only call this when button is released */ -/* lines and bytes need to be sorted already! */ -static void -copy_selection_to_x_primary(int line1, int byte1, int line2, int byte2) { - ledit_copy_text_to_lbuf(buffer, xsel.primary, line1, byte1, line2, byte2); - XSetSelectionOwner(state.dpy, XA_PRIMARY, state.win, CurrentTime); - /* - FIXME - if (XGetSelectionOwner(state.dpy, XA_PRIMARY) != state.win) - selclear(); */ + ledit_window_destroy(window); + XCloseDisplay(common.dpy); } static void -set_selection(int line1, int byte1, int line2, int byte2) { - if (line1 == buffer->sel.line1 && line2 == buffer->sel.line2 && - byte1 == buffer->sel.byte1 && byte2 == buffer->sel.byte2) { - return; - } - if (buffer->sel.line1 >= 0) { - int l1_new = line1, l2_new = line2; - int b1_new = byte1, b2_new = byte2; - sort_selection(&buffer->sel.line1, &buffer->sel.byte1, &buffer->sel.line2, &buffer->sel.byte2); - sort_selection(&l1_new, &b1_new, &l2_new, &b2_new); - if (buffer->sel.line1 > l2_new || buffer->sel.line2 < l1_new) { - for (int i = buffer->sel.line1; i <= buffer->sel.line2; i++) { - ledit_wipe_line_cursor_attrs(buffer, i); - } - } else { - for (int i = buffer->sel.line1; i < l1_new; i++) { - ledit_wipe_line_cursor_attrs(buffer, i); - } - for (int i = buffer->sel.line2; i > l2_new; i--) { - ledit_wipe_line_cursor_attrs(buffer, i); - } - } - if (l1_new == l2_new) { - ledit_set_line_selection(buffer, l1_new, b1_new, b2_new); - } else { - ledit_line *ll1 = ledit_get_line(buffer, l1_new); - ledit_set_line_selection(buffer, l1_new, b1_new, ll1->len); - ledit_set_line_selection(buffer, l2_new, 0, b2_new); - /* FIXME: optimize this */ - for (int i = l1_new + 1; i < l2_new; i++) { - if (i <= buffer->sel.line1 || i >= buffer->sel.line2) { - ledit_line *llx = ledit_get_line(buffer, i); - ledit_set_line_selection(buffer, i, 0, llx->len); - } - } - } - copy_selection_to_x_primary(l1_new, b1_new, l2_new, b2_new); - } - buffer->sel.line1 = line1; - buffer->sel.byte1 = byte1; - buffer->sel.line2 = line2; - buffer->sel.byte2 = byte2; +redraw(void) { + ledit_window_clear(window); + ledit_buffer_redraw(buffer); + ledit_window_redraw(window); } 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.text_w) { - 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; - } else if (y < state.text_h) { - int l, b; - xy_to_line_byte(x, y, &l, &b); - set_selection(l, b, l, b); - if (state.mode == NORMAL) { - ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); - set_mode(VISUAL); - } - buffer->cur_line = l; - buffer->cur_index = b; - state.selecting = 1; - return 1; - } - break; - case Button4: - buffer->display_offset -= SCROLL_STEP; - if (buffer->display_offset < 0) - buffer->display_offset = 0; - return 1; - case Button5: - if (buffer->display_offset + state.text_h < - buffer->total_height) { - buffer->display_offset += SCROLL_STEP; - if (buffer->display_offset + state.text_h > - buffer->total_height) { - buffer->display_offset = - buffer->total_height - state.text_h; - } - } - return 1; - } - return 0; + return ledit_window_button_press(window, event); } static int button_release(XEvent *event) { - if (event->xbutton.button == Button1) { - state.scroll_dragging = 0; - state.selecting = 0; - return 1; - } - return 0; + return ledit_window_button_release(window, event); } 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; - } else if (state.selecting) { - int l, b; - int y = event->xbutton.y >= 0 ? event->xbutton.y : 0; - xy_to_line_byte(event->xbutton.x, y, &l, &b); - set_selection(buffer->sel.line1, buffer->sel.byte1, l, b); - buffer->cur_line = l; - buffer->cur_index = b; - return 1; - } - return 0; -} - -static void -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.text_h) { - buffer->display_offset = - cursor_y - state.text_h + strong.height / PANGO_SCALE; - } + return ledit_window_drag_motion(window, event); } -/* FIXME: move the recalculation part of this to buffer.c */ static void resize_window(int w, int h) { - state.w = w; - state.h = h; - buffer->total_height = 0; - int tmp_w, tmp_h; - 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(line->layout, (w - SCROLLBAR_WIDTH) * PANGO_SCALE); - pango_layout_get_pixel_size(line->layout, &tmp_w, &tmp_h); - line->h = tmp_h; - line->w = w - SCROLLBAR_WIDTH; - line->y_offset = buffer->total_height; - line->dirty = 1; - buffer->total_height += tmp_h; - } - recalc_text_size(); - if (buffer->display_offset > 0 && - buffer->display_offset + state.text_h >= buffer->total_height) { - buffer->display_offset = buffer->total_height - state.text_h; - if (buffer->display_offset < 0) - buffer->display_offset = 0; - } -} - -static void -backspace(void) { - if (delete_selection()) { - /* NOP */ - } else if (buffer->cur_index == 0) { - if (buffer->cur_line != 0) { - ledit_line *l1 = ledit_get_line(buffer, buffer->cur_line - 1); - delete_range(0, 0, buffer->cur_line - 1, l1->len, buffer->cur_line, 0); - /* - int old_len = l1->len; - ledit_insert_text_from_line( - buffer, buffer->cur_line - 1, l1->len, - buffer->cur_line, 0, l2->len, tmp_buffer - ); - ledit_delete_line_entry(buffer, buffer->cur_line); - buffer->cur_line--; - buffer->cur_index = old_len; - */ - } - } else { - ledit_line *l = ledit_get_line(buffer, buffer->cur_line); - int i = ledit_prev_utf8(l, buffer->cur_index); - delete_range(0, 0, buffer->cur_line, buffer->cur_index, buffer->cur_line, i); - /* - buffer->cur_index = ledit_delete_unicode_char( - buffer, buffer->cur_line, buffer->cur_index, -1 - ); - */ - } - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); -} - -static void -delete_key(void) { - ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line); - if (delete_selection()) { - /* NOP */ - } else if (buffer->cur_index == cur_line->len) { - if (buffer->cur_line != buffer->lines_num - 1) { - delete_range(0, 0, buffer->cur_line, cur_line->len, buffer->cur_line + 1, 0); - /* - int old_len = cur_line->len; - ledit_insert_text_from_line( - buffer, buffer->cur_line, cur_line->len, - buffer->cur_line + 1, 0, -1 - ); - ledit_delete_line_entry(buffer, buffer->cur_line + 1); - */ - } - } else { - int i = ledit_next_utf8(cur_line, buffer->cur_index); - delete_range(0, 0, buffer->cur_line, buffer->cur_index, buffer->cur_line, i); - /* - buffer->cur_index = ledit_delete_unicode_char( - buffer, buffer->cur_line, buffer->cur_index, 1 - ); - */ - } - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); -} - -static void -move_cursor_left_right(int dir) { - int num = 1; - struct key_stack_elem *e = pop_key_stack(); - if (e != NULL) { - if (e->key & KEY_NUMBER) { - num = e->count > 0 ? e->count : 0; - e = pop_key_stack(); - } - if (e != NULL) - num *= (e->count > 0 ? e->count : 1); - } - - /* FIXME: trailing */ - int trailing = 0, tmp_index; - ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line); - int new_index = buffer->cur_index, last_index = buffer->cur_index; - while (num > 0) { - tmp_index = new_index; - pango_layout_move_cursor_visually( - cur_line->layout, TRUE, - new_index, trailing, dir, - &new_index, &trailing - ); - /* for some reason, this is necessary */ - if (new_index < 0) - new_index = 0; - else if (new_index > cur_line->len) - new_index = cur_line->len; - num--; - if (tmp_index != new_index) - last_index = tmp_index; - } - /* FIXME: Allow cursor to be at end of soft line */ - /* 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 */ - /* FIXME: spaces at end of softlines are weird in normal mode */ - while (trailing > 0) { - trailing--; - new_index = ledit_next_utf8(cur_line, new_index); - } - if (new_index < 0) - new_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 (new_index >= cur_line->len) { - if (state.mode == NORMAL && (e == NULL || e->motion_cb == NULL)) { - new_index = last_index; - } else { - /* FIXME: I guess this is unnecessary */ - new_index = cur_line->len; - } - } - if (e != NULL && e->motion_cb != NULL) { - e->motion_cb(buffer->cur_line, new_index, KEY_MOTION_CHAR); - } else { - buffer->cur_index = new_index; - if (state.mode == VISUAL) { - set_selection(buffer->sel.line1, buffer->sel.byte1, buffer->sel.line2, new_index); - } else if (state.mode == INSERT && - (buffer->sel.line1 != buffer->sel.line2 || - buffer->sel.byte1 != buffer->sel.byte2)) { - set_selection(buffer->cur_line, new_index, buffer->cur_line, new_index); - } else if (state.mode == NORMAL) { - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); - } - } - clear_key_stack(); + ledit_window_resize(window, w, h); + ledit_buffer_resize_textview(buffer); } static void -cursor_left(void) { - move_cursor_left_right(-1); -} - -static void -cursor_right(void) { - move_cursor_left_right(1); -} - -static void -return_key(void) { - int start_group = 1; - if (delete_selection()) - start_group = 0; - insert_text(buffer->cur_line, buffer->cur_index, "\n", -1, start_group); - /* 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 */ - /* 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) { - clear_key_stack(); /* just in case... */ - if (state.mode == INSERT && - (buffer->sel.line1 != buffer->sel.line2 || - buffer->sel.byte1 != buffer->sel.byte2)) { - set_mode(VISUAL); - } else { - set_mode(NORMAL); - clear_key_stack(); - PangoDirection dir = PANGO_DIRECTION_RTL; - 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(cur_line->layout, tmp_index); - if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_WEAK_RTL) { - cursor_right(); - } else { - cursor_left(); - } - if (buffer->sel.line1 != buffer->sel.line2) { - int min = buffer->sel.line1 < buffer->sel.line2 ? buffer->sel.line1 : buffer->sel.line2; - int max = buffer->sel.line1 > buffer->sel.line2 ? buffer->sel.line1 : buffer->sel.line2; - for (int i = min; i <= max; i++) { - ledit_wipe_line_cursor_attrs(buffer, i); - } - } - /* FIXME: optimize this to avoid first wiping and then setting the attrs */ - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); - } -} - -static void -enter_insert(void) { - if (state.mode == NORMAL) - ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); - set_mode(INSERT); - clear_key_stack(); -} - -/* FIXME: Check if previous key allows motion command - or is this checked automatically before? */ -static void -move_cursor_up_down(int dir) { - int new_line, new_softline; - - int num = 1; - struct key_stack_elem *e = pop_key_stack(); - if (e != NULL) { - if (e->key & KEY_NUMBER) { - num = e->count > 0 ? e->count : 0; - e = pop_key_stack(); - } - if (e != NULL) - num *= (e->count > 0 ? e->count : 1); - } - num *= dir; - - get_new_line_softline( - buffer->cur_line, buffer->cur_index, num, - &new_line, &new_softline - ); - - ledit_line *cur_lline = ledit_get_line(buffer, buffer->cur_line); - ledit_line *new_lline = ledit_get_line(buffer, new_line); - if (e != NULL && e->motion_cb != NULL) { - PangoLayoutLine *pl = pango_layout_get_line_readonly(new_lline->layout, new_softline); - e->motion_cb(new_line, pl->start_index, KEY_MOTION_LINE); - } else { - int lineno, x; - ledit_pos_to_x_softline(cur_lline, buffer->cur_index, &x, &lineno); - ledit_x_softline_to_pos(new_lline, x, new_softline, &buffer->cur_index); - if (buffer->cur_line != new_line) - ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); - buffer->cur_line = new_line; - - if (state.mode == VISUAL) { - set_selection(buffer->sel.line1, buffer->sel.byte1, buffer->cur_line, buffer->cur_index); - } else if (state.mode == INSERT && - (buffer->sel.line1 != buffer->sel.line2 || - buffer->sel.byte1 != buffer->sel.byte2)) { - set_selection(buffer->cur_line, buffer->cur_index, buffer->cur_line, buffer->cur_index); - } else if (state.mode == NORMAL) { - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); - } - } - clear_key_stack(); -} - -static void -cursor_down(void) { - move_cursor_up_down(1); -} - -static void -cursor_up(void) { - move_cursor_up_down(-1); +change_keyboard(char *lang) { + printf("%s\n", lang); + cur_lang = get_language_index(lang); + if (cur_lang < 0) + cur_lang = 0; } static void -cursor_to_beginning(void) { - struct key_stack_elem *e = pop_key_stack(); - /* FIXME: error when no callback? */ - if (e != NULL && e->motion_cb != NULL) { - e->motion_cb(buffer->cur_line, 0, KEY_MOTION_CHAR); +key_press(XEvent *event) { + if (cur_action.type == ACTION_GRABKEY && cur_action.callback) { + cur_action = cur_action.callback(buffer, event, cur_lang); } else { - buffer->cur_index = 0; - if (state.mode == VISUAL) { - set_selection( - buffer->sel.line1, buffer->sel.byte1, - buffer->cur_line, buffer->cur_index - ); - } else { - ledit_set_line_cursor_attrs( - buffer, buffer->cur_line, buffer->cur_index - ); - } - } - clear_key_stack(); -} - -static void -enter_visual(void) { - set_mode(VISUAL); - buffer->sel.line1 = buffer->sel.line2 = buffer->cur_line; - buffer->sel.byte1 = buffer->sel.byte2 = buffer->cur_index; - ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); - clear_key_stack(); /* FIXME: error if not empty? */ -} - -static void -switch_selection_end(void) { - swap(&buffer->sel.line1, &buffer->sel.line2); - swap(&buffer->sel.byte1, &buffer->sel.byte2); - buffer->cur_line = buffer->sel.line2; - buffer->cur_index = buffer->sel.byte2; -} - -#define XK_ANY_MOD UINT_MAX -#define XK_NO_MOD 0 - -static void -enter_commandedit(void) { - set_bottom_bar_text(":", -1); - bottom_bar.line_cur_pos = 1; - state.mode = COMMANDEDIT; - state.bottom_text_shown = 1; -} - -static void -enter_searchedit_forward(void) { - set_bottom_bar_text("/", -1); - bottom_bar.line_cur_pos = 1; - state.mode = SEARCHEDIT; - state.bottom_text_shown = 1; -} - -static void -enter_searchedit_backward(void) { - set_bottom_bar_text("?", -1); - bottom_bar.line_cur_pos = 1; - state.mode = SEARCHEDIT; - state.bottom_text_shown = 1; -} - -/* FIXME: support visual mode, i.e. change selection to new place? */ -static void -search_next(void) { - /* FIXME: avoid this when line doesn't change */ - ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); - enum ledit_search_state ret = ledit_search_next(buffer, &buffer->cur_line, &buffer->cur_index); - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); - if (ret != SEARCH_NORMAL) - show_message(ledit_search_state_to_str(ret), -1); -} - -static void -search_prev(void) { - /* FIXME: avoid this when line doesn't change */ - ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); - enum ledit_search_state ret = ledit_search_prev(buffer, &buffer->cur_line, &buffer->cur_index); - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); - if (ret != SEARCH_NORMAL) - show_message(ledit_search_state_to_str(ret), -1); -} - -static void -end_lineedit(void) { - enum ledit_mode old_mode = state.mode; - /* FIXME: go to last mode (visual or normal) */ - set_mode(NORMAL); - state.bottom_text_shown = 0; - - /* FIXME: forward/backward; check for empty string; - end edit mode when / or ? is deleted with backspace */ - if (old_mode == SEARCHEDIT) { - /* FIXME: this is all so horrible */ - if (bottom_bar.line_text[0] == '/') - ledit_set_search_forward(bottom_bar.line_text + 1); - else - ledit_set_search_backward(bottom_bar.line_text + 1); - search_next(); - } else if (old_mode == COMMANDEDIT) { - ledit_handle_cmd(buffer, bottom_bar.line_text + 1, -1); + cur_action = basic_key_handler(buffer, event, cur_lang); } } -static void -show_line(void) { - int len = snprintf(NULL, 0, "Line %d of %d", buffer->cur_line + 1, buffer->lines_num); - char *str = ledit_malloc(len + 1); - snprintf(str, len + 1, "Line %d of %d", buffer->cur_line + 1, buffer->lines_num); - show_message(str, len); -} - -/* FIXME: return status! */ -static void -undo(void) { - set_selection(0, 0, 0, 0); - ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); - ledit_undo(buffer); - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); -} - -static void -redo(void) { - set_selection(0, 0, 0, 0); - ledit_wipe_line_cursor_attrs(buffer, buffer->cur_line); - ledit_redo(buffer); - ledit_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index); -} - -/* FIXME: maybe sort these and use binary search */ -static struct key keys_en[] = { - {NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ANY, &backspace}, - {NULL, 0, XK_Left, VISUAL|INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_left}, - {NULL, 0, XK_Right, VISUAL|INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_right}, - {NULL, 0, XK_Up, VISUAL|INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_up}, - {NULL, 0, XK_Down, VISUAL|INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_down}, - {NULL, 0, XK_Return, INSERT, KEY_ANY, KEY_ANY, &return_key}, - {NULL, 0, XK_Delete, INSERT, KEY_ANY, KEY_ANY, &delete_key}, - {NULL, 0, XK_Escape, VISUAL|INSERT, KEY_ANY, KEY_ANY, &escape_key}, - {"i", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_insert}, - {"h", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_left}, - {"l", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_right}, - {"j", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_down}, - {"k", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_up}, - {"0", 0, 0, NORMAL|VISUAL, ~KEY_NUMBER, KEY_ANY, &cursor_to_beginning}, - {"0", 0, 0, NORMAL|VISUAL, KEY_NUMBER, KEY_NUMBER, &push_0}, - {"1", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_1}, - {"2", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_2}, - {"3", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_3}, - {"4", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_4}, - {"5", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_5}, - {"6", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_6}, - {"7", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_7}, - {"8", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_8}, - {"9", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBER, &push_9}, - {"x", 0, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &key_x}, - {"d", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION|KEY_NUMBERALLOWED, &key_d}, - {"v", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &enter_visual}, - {"o", 0, 0, VISUAL, KEY_ANY, KEY_ANY, &switch_selection_end}, - {"c", ControlMask, 0, INSERT|VISUAL, KEY_ANY, KEY_ANY, &clipcopy}, - {"v", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &clippaste}, - {"g", ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &show_line}, - {":", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_commandedit}, - {"?", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_searchedit_backward}, - {"/", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_searchedit_forward}, - {NULL, 0, XK_Return, COMMANDEDIT|SEARCHEDIT, KEY_ANY, KEY_ANY, &end_lineedit}, - {"n", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &search_next}, - {"N", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &search_prev}, - {"u", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &undo}, - {"U", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &redo}, - {"z", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &undo}, - {"y", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &redo} -}; - -static struct key keys_ur[] = { - {NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ANY, &backspace}, - {NULL, 0, XK_Left, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_left}, - {NULL, 0, XK_Right, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_right}, - {NULL, 0, XK_Up, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_up}, - {NULL, 0, XK_Down, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_down}, - {NULL, 0, XK_Return, INSERT, KEY_ANY, KEY_ANY, &return_key}, - {NULL, 0, XK_Delete, INSERT, KEY_ANY, KEY_ANY, &delete_key}, - {NULL, 0, XK_Escape, INSERT, KEY_ANY, KEY_ANY, &escape_key}, - {"ی", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &enter_insert}, - {"ح", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_left}, - {"ل", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_right}, - {"ج", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_down}, - {"ک", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_up}, - {"0", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &cursor_to_beginning}, - {"چ", ControlMask, 0, INSERT|VISUAL, KEY_ANY, KEY_ANY, &clipcopy} -}; - -static struct key keys_hi[] = { - {NULL, 0, XK_BackSpace, INSERT, KEY_ANY, KEY_ANY, &backspace}, - {NULL, 0, XK_Left, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_left}, - {NULL, 0, XK_Right, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_right}, - {NULL, 0, XK_Up, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_up}, - {NULL, 0, XK_Down, INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_down}, - {NULL, 0, XK_Return, INSERT, KEY_ANY, KEY_ANY, &return_key}, - {NULL, 0, XK_Delete, INSERT, KEY_ANY, KEY_ANY, &delete_key}, - {NULL, 0, XK_Escape, INSERT, KEY_ANY, KEY_ANY, &escape_key}, - {"ि", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &enter_insert}, - {"ह", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_left}, - {"ल", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_right}, - {"ज", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_down}, - {"क", 0, 0, NORMAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_up}, - {"0", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &cursor_to_beginning} -}; - -#define LENGTH(X) (sizeof(X) / sizeof(X[0])) - -struct lang_keys { - char *lang; - struct key *keys; - int num_keys; -}; - -static struct lang_keys keys[] = { - {"English (US)", keys_en, LENGTH(keys_en)}, - {"German", keys_en, LENGTH(keys_en)}, - {"Urdu (Pakistan)", keys_ur, LENGTH(keys_ur)}, - {"Hindi (Bolnagri)", keys_hi, LENGTH(keys_hi)} -}; - -static struct lang_keys *cur_keys = &keys[0]; - -static void change_keyboard(char *lang) { - printf("%s\n", lang); - for (size_t i = 0; i < LENGTH(keys); i++) { - if (!strcmp(keys[i].lang, lang)) { - cur_keys = &keys[i]; - break; - } - } -} - -/* FIXME: Does this break anything? */ -static unsigned int importantmod = ShiftMask | ControlMask | Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask; - -static int -match(unsigned int mask, unsigned int state) -{ - return mask == XK_ANY_MOD || mask == (state & importantmod); -} +int +main(int argc, char *argv[]) { + setup(argc, argv); + mainloop(); + cleanup(); -static void -key_press(XEvent event) { - char buf[32]; - KeySym sym; - /* - * FIXME: I don't understand how key handling works with different keymaps. - * If, for instance, you run "setxkbmap pk" and then type "Ctrl+c", the - * keysym will be "0x1000686, Arabic_tcheh" and the appropriate string will - * be returned by XmbLookupString (tested also using xev). - * If, however, you run "setxkbmap -layout "us,pk" -option grp:shifts_toggle" - * and type "Ctrl+c" after switching to the pk layout, the keysym will just - * be "0x63, c" and XmbLookupString will return the control code sent by - * Ctrl+c (ascii 03). This means the handling is different depending on how - * the keymap is switched to, and I have no clue what the proper way to - * handle this would be, since the shortcuts are explicitly supposed to work - * properly in all language maps. My current solution is to just ignore the - * control key in the state so the text found by Xutf8LookupString is correct - * and the modifier mask can be checked separately. Please tell me if you - * know the proper way to do this. - */ - unsigned int key_state = event.xkey.state; - event.xkey.state &= ~ControlMask; - /* FIXME: X_HAVE_UTF8_STRING See XmbLookupString(3) */ - int n = Xutf8LookupString( - state.xic, &event.xkey, buf, sizeof(buf), &sym, NULL - ); - int found = 0; - struct key_stack_elem *e = peek_key_stack(); - for (int i = 0; i < cur_keys->num_keys; i++) { - if (cur_keys->keys[i].text) { - if (n > 0 && - (cur_keys->keys[i].modes & state.mode) && - (!e || (e->key & cur_keys->keys[i].prev_keys)) && - !strncmp(cur_keys->keys[i].text, buf, n) && - match(cur_keys->keys[i].mods, key_state & ~ShiftMask)) { - /* FIXME: seems a bit hacky to remove shift, but it - is needed to make keys that use shift match */ - cur_keys->keys[i].func(); - found = 1; - break; - } - } else if ((cur_keys->keys[i].modes & state.mode) && - cur_keys->keys[i].keysym == sym && - match(cur_keys->keys[i].mods, key_state)) { - cur_keys->keys[i].func(); - found = 1; - break; - } - } - if (found) { - /* FIXME: only do this when necessary */ - ensure_cursor_shown(); - /* FIXME: this is a bit hacky */ - if (state.message_shown > 0) - state.message_shown--; - } else if (state.mode == INSERT && !found && n > 0) { - delete_selection(); - insert_text(buffer->cur_line, buffer->cur_index, buf, n, 1); - /* - ledit_insert_text_with_newlines( - buffer, - buffer->cur_line, buffer->cur_index, - buf, n, - &buffer->cur_line, &buffer->cur_index - ); - */ - ensure_cursor_shown(); - if (state.message_shown > 0) - state.message_shown--; - } else if (!found && (state.mode == COMMANDEDIT || state.mode == SEARCHEDIT) && n > 0) { - insert_bottom_bar_text(buf, n); - bottom_bar.line_cur_pos += n; - } + return 0; } diff --git a/search.c b/search.c @@ -1,4 +1,8 @@ +/* FIXME: split buffer into pure text part and graphical part so this + * doesn't depend on all the graphics stuff */ + #include <string.h> + #include <X11/Xlib.h> #include <X11/Xutil.h> #include <pango/pangoxft.h> @@ -6,7 +10,11 @@ #include "memory.h" #include "common.h" -#include "lbuf.h" +#include "txtbuf.h" +#include "undo.h" +#include "cache.h" +#include "theme.h" +#include "window.h" #include "buffer.h" #include "search.h" @@ -48,16 +56,16 @@ search_forward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { note: since the string ends with '\0', this is always valid */ int byte = buffer->cur_index + 1; char *res; - ledit_line *lline = ledit_get_line(buffer, line); - ledit_normalize_line(lline); + ledit_line *lline = ledit_buffer_get_line(buffer, line); + ledit_buffer_normalize_line(lline); if ((res = strstr(lline->text + byte, last_search)) != NULL) { *line_ret = line; *byte_ret = (int)(res - lline->text); return SEARCH_NORMAL; } for (int i = line + 1; i < buffer->lines_num; i++) { - lline = ledit_get_line(buffer, i); - ledit_normalize_line(lline); + lline = ledit_buffer_get_line(buffer, i); + ledit_buffer_normalize_line(lline); if ((res = strstr(lline->text, last_search)) != NULL) { *line_ret = i; *byte_ret = (int)(res - lline->text); @@ -65,16 +73,16 @@ search_forward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { } } for (int i = 0; i < line; i++) { - lline = ledit_get_line(buffer, i); - ledit_normalize_line(lline); + lline = ledit_buffer_get_line(buffer, i); + ledit_buffer_normalize_line(lline); if ((res = strstr(lline->text, last_search)) != NULL) { *line_ret = i; *byte_ret = (int)(res - lline->text); return SEARCH_WRAPPED; } } - lline = ledit_get_line(buffer, line); - ledit_normalize_line(lline); + lline = ledit_buffer_get_line(buffer, line); + ledit_buffer_normalize_line(lline); if ((res = strstr(lline->text, last_search)) != NULL) { *line_ret = line; *byte_ret = (int)(res - lline->text); @@ -92,8 +100,8 @@ search_backward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { return SEARCH_NO_PATTERN; int line = buffer->cur_line; int byte = buffer->cur_index; - ledit_line *lline = ledit_get_line(buffer, line); - ledit_normalize_line(lline); + ledit_line *lline = ledit_buffer_get_line(buffer, line); + ledit_buffer_normalize_line(lline); char *last = NULL, *res = lline->text; while ((res = strstr(res, last_search)) != NULL && res < lline->text + byte) { last = res; @@ -106,8 +114,8 @@ search_backward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { return SEARCH_NORMAL; } for (int i = line - 1; i >= 0; i--) { - lline = ledit_get_line(buffer, i); - ledit_normalize_line(lline); + lline = ledit_buffer_get_line(buffer, i); + ledit_buffer_normalize_line(lline); res = lline->text; while ((res = strstr(res, last_search)) != NULL) { last = res; @@ -120,8 +128,8 @@ search_backward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { } } for (int i = buffer->lines_num - 1; i > line; i--) { - lline = ledit_get_line(buffer, i); - ledit_normalize_line(lline); + lline = ledit_buffer_get_line(buffer, i); + ledit_buffer_normalize_line(lline); res = lline->text; while ((res = strstr(res, last_search)) != NULL) { last = res; @@ -133,8 +141,8 @@ search_backward(ledit_buffer *buffer, int *line_ret, int *byte_ret) { return SEARCH_WRAPPED; } } - lline = ledit_get_line(buffer, line); - ledit_normalize_line(lline); + lline = ledit_buffer_get_line(buffer, line); + ledit_buffer_normalize_line(lline); res = lline->text + byte; while ((res = strstr(res, last_search)) != NULL) { last = res; diff --git a/theme.c b/theme.c @@ -0,0 +1,35 @@ +#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 * +ledit_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_size = TEXT_SIZE; + theme->text_fg_hex = TEXT_FG; + theme->text_bg_hex = TEXT_BG; + 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, SCROLLBAR_FG, &theme->scrollbar_fg); + XftColorAllocName(common->dpy, common->vis, common->cm, SCROLLBAR_BG, &theme->scrollbar_bg); + return theme; +} + +void +ledit_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->scrollbar_fg); + XftColorFree(common->dpy, common->vis, common->cm, &theme->scrollbar_bg); + free(theme); +} diff --git a/theme.h b/theme.h @@ -0,0 +1,16 @@ +typedef struct { + int scrollbar_width; + int scrollbar_step; + int text_size; + XftColor text_fg; + XftColor text_bg; + XftColor scrollbar_fg; + XftColor scrollbar_bg; + const char *text_fg_hex; + const char *text_bg_hex; + const char *scrollbar_fg_hex; + const char *scrollbar_bg_hex; +} ledit_theme; + +ledit_theme *ledit_theme_create(ledit_common *common); +void ledit_theme_destroy(ledit_common *common, ledit_theme *theme); diff --git a/theme_config.h b/theme_config.h @@ -0,0 +1,9 @@ +static const int TEXT_SIZE = 12; +static const char *TEXT_FG = "#000000"; +static const char *TEXT_BG = "#FFFFFF"; + +/* FIXME: give in units other than pixels */ +static const int SCROLLBAR_WIDTH = 10; +static const int SCROLLBAR_STEP = 10; +static const char *SCROLLBAR_BG = "#CCCCCC"; +static const char *SCROLLBAR_FG = "#000000"; diff --git a/txtbuf.c b/txtbuf.c @@ -0,0 +1,51 @@ +#include <stdlib.h> +#include <string.h> + +#include "memory.h" +#include "txtbuf.h" + +txtbuf * +txtbuf_new(void) { + txtbuf *buf = ledit_malloc(sizeof(txtbuf)); + buf->text = NULL; + buf->cap = buf->len = 0; + return buf; +} + +void +txtbuf_grow(txtbuf *buf, size_t sz) { + /* always leave room for extra \0 */ + if (sz + 1 > buf->cap) { + /* FIXME: what are the best values here? */ + buf->cap = buf->cap * 2 > sz + 1 ? buf->cap * 2 : sz + 1; + buf->text = ledit_realloc(buf->text, buf->cap); + } +} + +void +txtbuf_shrink(txtbuf *buf) { + if ((buf->len + 1) * 4 < buf->cap) { + buf->cap /= 2; + buf->text = ledit_realloc(buf->text, buf->cap); + } +} + +void +txtbuf_destroy(txtbuf *buf) { + free(buf->text); + free(buf); +} + +void +txtbuf_copy(txtbuf *dst, txtbuf *src) { + txtbuf_grow(dst, src->len); + memcpy(dst->text, src->text, src->len); + dst->len = src->len; +} + +txtbuf * +txtbuf_dup(txtbuf *src) { + txtbuf *dst = txtbuf_new(); + txtbuf_copy(dst, src); + return dst; +} diff --git a/txtbuf.h b/txtbuf.h @@ -0,0 +1,11 @@ +typedef struct { + size_t len, cap; + char *text; +} txtbuf; + +txtbuf *txtbuf_new(void); +void txtbuf_grow(txtbuf *buf, size_t sz); +void txtbuf_shrink(txtbuf *buf); +void txtbuf_destroy(txtbuf *buf); +void txtbuf_copy(txtbuf *dst, txtbuf *src); +txtbuf *txtbuf_dup(txtbuf *src); diff --git a/undo.c b/undo.c @@ -5,14 +5,10 @@ #include <X11/Xlib.h> #include <X11/Xutil.h> #include <pango/pangoxft.h> -/* FIXME: move some parts of common to ledit.c so - this include isn't needed */ -#include <X11/extensions/Xdbe.h> #include "memory.h" #include "common.h" -#include "lbuf.h" -#include "buffer.h" +#include "txtbuf.h" #include "cache.h" #include "undo.h" @@ -22,7 +18,7 @@ enum operation { }; typedef struct { - lbuf *text; + txtbuf *text; enum operation type; enum ledit_mode mode; ledit_range op_range; @@ -38,119 +34,117 @@ struct ledit_undo_stack { int change_mode_group; }; -/* FIXME: maybe make these work directly on the stack instead of buffer */ -void -ledit_init_undo_stack(ledit_buffer *buffer) { - buffer->undo = ledit_malloc(sizeof(ledit_undo_stack)); - buffer->undo->len = buffer->undo->cap = 0; - buffer->undo->cur = -1; - buffer->undo->stack = NULL; - buffer->undo->change_mode_group = 0; +ledit_undo_stack * +ledit_undo_stack_create(void) { + ledit_undo_stack *undo = ledit_malloc(sizeof(ledit_undo_stack)); + undo->len = undo->cap = 0; + undo->cur = -1; + undo->stack = NULL; + undo->change_mode_group = 0; + return undo; } void -ledit_destroy_undo_stack(ledit_buffer *buffer) { - free(buffer->undo->stack); - free(buffer->undo); +ledit_undo_stack_destroy(ledit_undo_stack *undo) { + free(undo->stack); + free(undo); } /* FIXME: resize text buffers when they aren't needed anymore */ static undo_elem * -push_undo_elem(ledit_buffer *buffer) { - ledit_undo_stack *s = buffer->undo; - assert(s->cur >= -1); - s->cur++; - s->len = s->cur + 1; - if (s->len > s->cap) { - size_t cap = s->len * 2; - s->stack = ledit_realloc(s->stack, cap * sizeof(undo_elem)); - for (size_t i = s->cap; i < cap; i++) { - s->stack[i].text = NULL; +push_undo_elem(ledit_undo_stack *undo) { + assert(undo->cur >= -1); + undo->cur++; + undo->len = undo->cur + 1; + if (undo->len > undo->cap) { + size_t cap = undo->len * 2; + undo->stack = ledit_realloc(undo->stack, cap * sizeof(undo_elem)); + for (size_t i = undo->cap; i < cap; i++) { + undo->stack[i].text = NULL; } - s->cap = cap; + undo->cap = cap; } - return &s->stack[s->cur]; + return &undo->stack[undo->cur]; } static undo_elem * -peek_undo_elem(ledit_buffer *buffer) { - ledit_undo_stack *s = buffer->undo; - if (s->cur < 0) +peek_undo_elem(ledit_undo_stack *undo) { + if (undo->cur < 0) return NULL; - return &s->stack[s->cur]; + return &undo->stack[undo->cur]; } void -ledit_change_mode_group(ledit_buffer *buffer) { - buffer->undo->change_mode_group = 1; +ledit_change_mode_group(ledit_undo_stack *undo) { + undo->change_mode_group = 1; } -/* FIXME: The current cursor position could be taken directly from the - buffer, but maybe it's better this way to make it a bit more explicit? */ static void push_undo( - ledit_buffer *buffer, lbuf *text, + ledit_undo_stack *undo, txtbuf *text, ledit_range insert_range, ledit_range cursor_range, - int start_group, enum operation type) { - undo_elem *old = peek_undo_elem(buffer); + int start_group, enum operation type, + enum ledit_mode mode) { + undo_elem *old = peek_undo_elem(undo); int last_group = old == NULL ? 0 : old->group; int last_mode_group = old == NULL ? 0 : old->mode_group; - undo_elem *e = push_undo_elem(buffer); + undo_elem *e = push_undo_elem(undo); e->group = start_group ? !last_group : last_group; - e->mode_group = buffer->undo->change_mode_group ? !last_mode_group : last_mode_group; - buffer->undo->change_mode_group = 0; + e->mode_group = undo->change_mode_group ? !last_mode_group : last_mode_group; + undo->change_mode_group = 0; e->op_range = insert_range; e->cursor_range = cursor_range; - e->mode = buffer->state->mode; + e->mode = mode; e->type = type; if (e->text != NULL) - lbuf_cpy(e->text, text); + txtbuf_copy(e->text, text); else - e->text = lbuf_dup(text); + e->text = txtbuf_dup(text); } void ledit_push_undo_insert( - ledit_buffer *buffer, lbuf *text, + ledit_undo_stack *undo, txtbuf *text, ledit_range insert_range, ledit_range cursor_range, - int start_group) { + int start_group, enum ledit_mode mode) { push_undo( - buffer, text, insert_range, - cursor_range, start_group, UNDO_INSERT + undo, text, insert_range, cursor_range, + start_group, UNDO_INSERT, mode ); } void ledit_push_undo_delete( - ledit_buffer *buffer, lbuf *text, + ledit_undo_stack *undo, txtbuf *text, ledit_range insert_range, ledit_range cursor_range, - int start_group) { + int start_group, enum ledit_mode mode) { push_undo( - buffer, text, insert_range, - cursor_range, start_group, UNDO_DELETE + undo, text, insert_range, cursor_range, + start_group, UNDO_DELETE, mode ); } ledit_undo_status -ledit_undo(ledit_buffer *buffer) { +ledit_undo(ledit_undo_stack *undo, enum ledit_mode mode, void *callback_data, + undo_insert_callback insert_cb, undo_delete_callback delete_cb, + int *cur_line_ret, int *cur_index_ret, int *min_line_ret) { undo_elem *e; - ledit_undo_stack *s = buffer->undo; - if (s->cur < 0) + if (undo->cur < 0) return UNDO_OLDEST_CHANGE; - int group = s->stack[s->cur].group; - int mode_group = s->stack[s->cur].mode_group; - int min_line = buffer->lines_num - 1; + int group = undo->stack[undo->cur].group; + int mode_group = undo->stack[undo->cur].mode_group; + int min_line = INT_MAX; int mode_group_same = 0; - while (s->cur >= 0 && - (s->stack[s->cur].group == group || (mode_group_same = - ((buffer->state->mode == NORMAL || - buffer->state->mode == VISUAL) && - s->stack[s->cur].mode == INSERT && - s->stack[s->cur].mode_group == mode_group)))) { - e = &s->stack[s->cur]; + while (undo->cur >= 0 && + (undo->stack[undo->cur].group == group || (mode_group_same = + ((mode == NORMAL || + mode == VISUAL) && + undo->stack[undo->cur].mode == INSERT && + undo->stack[undo->cur].mode_group == mode_group)))) { + e = &undo->stack[undo->cur]; /* if the mode group is the same, we need to update the group, otherwise it can happen that some iterations are performed because the mode group (but not the normal group) is the @@ -161,17 +155,17 @@ ledit_undo(ledit_buffer *buffer) { switch (e->type) { case UNDO_INSERT: /* FIXME: should the paste buffer also be modified? */ - ledit_delete_range_base( - buffer, 0, + delete_cb( + callback_data, e->op_range.line1, e->op_range.byte1, - e->op_range.line2, e->op_range.byte2, - NULL, NULL, NULL, NULL + e->op_range.line2, e->op_range.byte2 ); break; case UNDO_DELETE: - ledit_insert_text_with_newlines_base( - buffer, e->op_range.line1, e->op_range.byte1, - e->text->text, e->text->len, NULL, NULL + insert_cb( + callback_data, + e->op_range.line1, e->op_range.byte1, + e->text->text, e->text->len ); break; default: @@ -181,52 +175,48 @@ ledit_undo(ledit_buffer *buffer) { /* FIXME: make sure this is always sorted already */ if (e->op_range.line1 < min_line) min_line = e->op_range.line1; - s->cur--; - buffer->cur_line = e->cursor_range.line1; - buffer->cur_index = e->cursor_range.byte1; - } - if (buffer->state->mode == NORMAL) { - buffer->cur_index = ledit_get_legal_normal_pos( - buffer, buffer->cur_line, buffer->cur_index - ); + undo->cur--; + *cur_line_ret = e->cursor_range.line1; + *cur_index_ret = e->cursor_range.byte1; } - ledit_recalc_from_line(buffer, min_line > 0 ? min_line - 1 : min_line); + *min_line_ret = min_line; return UNDO_NORMAL; } ledit_undo_status -ledit_redo(ledit_buffer *buffer) { +ledit_redo(ledit_undo_stack *undo, enum ledit_mode mode, void *callback_data, + undo_insert_callback insert_cb, undo_delete_callback delete_cb, + int *cur_line_ret, int *cur_index_ret, int *min_line_ret) { undo_elem *e; - ledit_undo_stack *s = buffer->undo; - if (s->cur >= s->len - 1) + if (undo->cur >= undo->len - 1) return UNDO_NEWEST_CHANGE; - s->cur++; - int group = s->stack[s->cur].group; - int mode_group = s->stack[s->cur].mode_group; - int min_line = buffer->lines_num - 1; + undo->cur++; + int group = undo->stack[undo->cur].group; + int mode_group = undo->stack[undo->cur].mode_group; + int min_line = INT_MAX; int mode_group_same = 0; - while (s->cur < s->len && - (s->stack[s->cur].group == group || (mode_group_same = - ((buffer->state->mode == NORMAL || - buffer->state->mode == VISUAL) && - s->stack[s->cur].mode == INSERT && - s->stack[s->cur].mode_group == mode_group)))) { - e = &s->stack[s->cur]; + while (undo->cur < undo->len && + (undo->stack[undo->cur].group == group || (mode_group_same = + ((mode == NORMAL || + mode == VISUAL) && + undo->stack[undo->cur].mode == INSERT && + undo->stack[undo->cur].mode_group == mode_group)))) { + e = &undo->stack[undo->cur]; if (mode_group_same) group = e->group; switch (e->type) { case UNDO_INSERT: - ledit_insert_text_with_newlines_base( - buffer, e->op_range.line1, e->op_range.byte1, - e->text->text, e->text->len, NULL, NULL + insert_cb( + callback_data, + e->op_range.line1, e->op_range.byte1, + e->text->text, e->text->len ); break; case UNDO_DELETE: - ledit_delete_range_base( - buffer, 0, + delete_cb( + callback_data, e->op_range.line1, e->op_range.byte1, - e->op_range.line2, e->op_range.byte2, - NULL, NULL, NULL, NULL + e->op_range.line2, e->op_range.byte2 ); break; default: @@ -235,16 +225,11 @@ ledit_redo(ledit_buffer *buffer) { } if (e->op_range.line1 < min_line) min_line = e->op_range.line1; - s->cur++; - buffer->cur_line = e->cursor_range.line2; - buffer->cur_index = e->cursor_range.byte2; - } - s->cur--; - if (buffer->state->mode == NORMAL) { - buffer->cur_index = ledit_get_legal_normal_pos( - buffer, buffer->cur_line, buffer->cur_index - ); + undo->cur++; + *cur_line_ret = e->cursor_range.line2; + *cur_index_ret = e->cursor_range.byte2; } - ledit_recalc_from_line(buffer, min_line > 0 ? min_line - 1 : min_line); + undo->cur--; + *min_line_ret = min_line; return UNDO_NORMAL; } diff --git a/undo.h b/undo.h @@ -4,20 +4,30 @@ typedef enum { UNDO_NEWEST_CHANGE } ledit_undo_status; -void ledit_init_undo_stack(ledit_buffer *buffer); -void ledit_destroy_undo_stack(ledit_buffer *buffer); -ledit_undo_status ledit_undo(ledit_buffer *buffer); -ledit_undo_status ledit_redo(ledit_buffer *buffer); +typedef struct ledit_undo_stack ledit_undo_stack; +typedef void (*undo_insert_callback)(void *, int, int, char *, int); +typedef void (*undo_delete_callback)(void *, int, int, int, int); + +ledit_undo_stack *ledit_undo_stack_create(void); +void ledit_undo_stack_destroy(ledit_undo_stack *undo); +void ledit_change_mode_group(ledit_undo_stack *undo); void ledit_push_undo_insert( - ledit_buffer *buffer, lbuf *text, - ledit_range insert_range, - ledit_range cursor_range, - int start_group + ledit_undo_stack *undo, txtbuf *text, + ledit_range insert_range, ledit_range cursor_range, + int start_group, enum ledit_mode mode ); void ledit_push_undo_delete( - ledit_buffer *buffer, lbuf *text, - ledit_range insert_range, - ledit_range cursor_range, - int start_group + ledit_undo_stack *undo, txtbuf *text, + ledit_range insert_range, ledit_range cursor_range, + int start_group, enum ledit_mode mode +); +ledit_undo_status ledit_undo( + ledit_undo_stack *undo, enum ledit_mode mode, + void *callback_data, undo_insert_callback insert_cb, undo_delete_callback delete_cb, + int *cur_line_ret, int *cur_index_ret, int *min_line_ret +); +ledit_undo_status ledit_redo( + ledit_undo_stack *undo, enum ledit_mode mode, + void *callback_data, undo_insert_callback insert_cb, undo_delete_callback delete_cb, + int *cur_line_ret, int *cur_index_ret, int *min_line_ret ); -void ledit_change_mode_group(ledit_buffer *buffer); diff --git a/util.c b/util.c @@ -5,33 +5,36 @@ #include "memory.h" #include "common.h" +#include "txtbuf.h" +#include "theme.h" +#include "window.h" #include "util.h" ledit_draw * -ledit_create_draw(ledit_common_state *state, int w, int h) { +ledit_draw_create(ledit_window *window, int w, int h) { ledit_draw *draw = ledit_malloc(sizeof(ledit_draw)); draw->w = w; draw->h = h; draw->pixmap = XCreatePixmap( - state->dpy, state->drawable, w, h, state->depth + window->common->dpy, window->drawable, w, h, window->common->depth ); draw->xftdraw = XftDrawCreate( - state->dpy, draw->pixmap, state->vis, state->cm + window->common->dpy, draw->pixmap, window->common->vis, window->common->cm ); return draw; } void -ledit_grow_draw(ledit_common_state *state, ledit_draw *draw, int w, int h) { +ledit_draw_grow(ledit_window *window, ledit_draw *draw, int w, int h) { /* FIXME: sensible default pixmap sizes here */ /* FIXME: maybe shrink the pixmaps at some point */ if (draw->w < w || draw->h < h) { draw->w = w > draw->w ? w + 10 : draw->w; draw->h = h > draw->h ? h + 10 : draw->h; - XFreePixmap(state->dpy, draw->pixmap); + XFreePixmap(window->common->dpy, draw->pixmap); draw->pixmap = XCreatePixmap( - state->dpy, state->drawable, - draw->w, draw->h, state->depth + window->common->dpy, window->drawable, + draw->w, draw->h, window->common->depth ); XftDrawChange(draw->xftdraw, draw->pixmap); } diff --git a/util.h b/util.h @@ -4,5 +4,5 @@ typedef struct { int w, h; } ledit_draw; -ledit_draw *ledit_create_draw(ledit_common_state *state, int w, int h); -void ledit_grow_draw(ledit_common_state *state, ledit_draw *draw, int w, int h); +ledit_draw *ledit_draw_create(ledit_window *window, int w, int h); +void ledit_draw_grow(ledit_window *window, ledit_draw *draw, int w, int h); diff --git a/window.c b/window.c @@ -0,0 +1,769 @@ +#include <math.h> +#include <stdio.h> +#include <assert.h> +#include <stdlib.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#include <pango/pangoxft.h> +#include <X11/XKBlib.h> +#include <X11/extensions/XKBrules.h> +#include <X11/extensions/Xdbe.h> + +#include "memory.h" +#include "common.h" +#include "txtbuf.h" +#include "theme.h" +#include "window.h" +#include "util.h" + +struct bottom_bar { + /* FIXME: encapsulate layout, width, draw a bit */ + PangoLayout *mode; + ledit_draw *mode_draw; + int mode_w, mode_h; + PangoLayout *ruler; + ledit_draw *ruler_draw; + int ruler_w, ruler_h; + PangoLayout *line; + ledit_draw *line_draw; + int line_w, line_h; + char *line_text; + int line_alloc, line_len; + int line_cur_pos; +}; + +/* clipboard handling largely stolen from st (simple terminal) */ + +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) + +struct { + txtbuf *primary; + char *clipboard; +} xsel = {NULL, NULL}; + +static void recalc_text_size(ledit_window *window); +static void get_scroll_pos_height(ledit_window *windown, double *pos, double *height); +static void set_scroll_pos(ledit_window *window, double pos); +static void clipboard_propnotify(ledit_window *window, XEvent *e); +static void clipboard_selnotify(ledit_window *window, XEvent *e); +static void clipboard_selrequest(ledit_window *window, XEvent *e); + +txtbuf * +ledit_window_get_primary_clipboard_buffer(void) { + /* FIXME: check if NULL */ + return xsel.primary; +} + +/* FIXME static void ensure_cursor_shown(void); */ + +/* FIXME: shouldn't window->bottom_text_shown also be true when message_shown? */ +static void +recalc_text_size(ledit_window *window) { + int bar_h = window->bb->mode_h; + if ((window->bottom_text_shown || window->message_shown) && window->bb->line_h > bar_h) + bar_h = window->bb->line_h; + window->text_w = window->w - window->theme->scrollbar_width; + window->text_h = window->h - bar_h; +} + +/* FIXME: allow lines longer than window width to be displayed properly */ +void +ledit_window_insert_bottom_bar_text(ledit_window *window, char *text, int len) { + assert(len >= -1); + assert(window->bb->line_cur_pos <= window->bb->line_alloc); + + if (len == -1) + len = strlen(text); + /* \0 not included in len */ + if (window->bb->line_len + len + 1 > window->bb->line_alloc || window->bb->line_text == NULL) { + /* FIXME: read up on what the best values are here */ + window->bb->line_alloc = + window->bb->line_alloc * 2 > window->bb->line_len + len + 1 ? + window->bb->line_alloc * 2 : + window->bb->line_len + len + 1; + window->bb->line_text = ledit_realloc(window->bb->line_text, window->bb->line_alloc); + } + memmove( + window->bb->line_text + window->bb->line_cur_pos + len, + window->bb->line_text + window->bb->line_cur_pos, + window->bb->line_len - window->bb->line_cur_pos + ); + memcpy(window->bb->line_text + window->bb->line_cur_pos, text, len); + window->bb->line_len += len; + window->bb->line_text[window->bb->line_len] = '\0'; + 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); + ledit_draw_grow(window, window->bb->line_draw, window->bb->line_w, window->bb->line_h); + XftDrawRect(window->bb->line_draw->xftdraw, &window->theme->text_bg, 0, 0, window->bb->line_w, window->bb->line_h); + pango_xft_render_layout(window->bb->line_draw->xftdraw, &window->theme->text_fg, window->bb->line, 0, 0); + recalc_text_size(window); +} + +void +ledit_window_set_bottom_bar_cursor(ledit_window *window, int byte_pos) { + /* FIXME: check if valid? */ + window->bb->line_cur_pos = byte_pos; +} + +int +ledit_window_get_bottom_bar_cursor(ledit_window *window) { + return window->bb->line_cur_pos; +} + +void +ledit_window_set_bottom_bar_text_shown(ledit_window *window, int shown) { + window->bottom_text_shown = shown; +} + +int +ledit_window_bottom_bar_text_shown(ledit_window *window) { + return window->bottom_text_shown; +} + +void +ledit_window_set_bottom_bar_text(ledit_window *window, char *text, int len) { + window->bb->line_len = 0; + window->bb->line_cur_pos = 0; + ledit_window_insert_bottom_bar_text(window, text, len); +} + +char * +ledit_window_get_bottom_bar_text(ledit_window *window) { + return window->bb->line_text; +} + +void +ledit_window_show_message(ledit_window *window, char *text, int len) { + ledit_window_set_bottom_bar_text(window, text, len); + /* FIXME: rename these */ + window->bottom_text_shown = 0; + window->message_shown = 1; +} + +int +ledit_window_message_shown(ledit_window *window) { + return window->message_shown; +} + +void +ledit_window_hide_message(ledit_window *window) { + window->message_shown = 0; +} + +void +ledit_window_set_mode(ledit_window *window, enum ledit_mode mode) { + switch (mode) { + case NORMAL: + pango_layout_set_text(window->bb->mode, "Normal", -1); + break; + case VISUAL: + pango_layout_set_text(window->bb->mode, "Visual", -1); + break; + case INSERT: + pango_layout_set_text(window->bb->mode, "Insert", -1); + break; + default: + pango_layout_set_text(window->bb->mode, "ledit is buggy", -1); + break; + } + pango_layout_get_pixel_size(window->bb->mode, &window->bb->mode_w, &window->bb->mode_h); + ledit_draw_grow(window, window->bb->mode_draw, window->bb->mode_w, window->bb->mode_h); + XftDrawRect(window->bb->mode_draw->xftdraw, &window->theme->text_bg, 0, 0, window->bb->mode_w, window->bb->mode_h); + pango_xft_render_layout(window->bb->mode_draw->xftdraw, &window->theme->text_fg, window->bb->mode, 0, 0); + recalc_text_size(window); +} + +/* FIXME: give these functions more sensible names */ +static void +get_scroll_pos_height(ledit_window *window, double *pos, double *height) { + *height = ((double)window->text_h / window->scroll_max) * window->text_h; + *pos = (window->scroll_offset / + (window->scroll_max - window->text_h)) * (window->text_h - *height); +} + +static void +set_scroll_pos(ledit_window *window, double pos) { + window->scroll_offset = pos * (window->scroll_max / (double)window->text_h); + if (window->scroll_offset < 0) + window->scroll_offset = 0; + if (window->scroll_offset + window->text_h > window->scroll_max) + window->scroll_offset = window->scroll_max - window->text_h; + /* FIXME: check for overflow */ + if (window->scroll_callback) + window->scroll_callback(window->scroll_cb_data, (long)window->scroll_offset); +} + +void +ledit_window_set_scroll_max(ledit_window *window, long max) { + window->scroll_max = max; + /* FIXME: set offset if too large */ + /* actually not, because buffer does that already */ + /* FIXME: SHOULD THIS EVEN BE USED? */ + window->common->redraw = 1; +} + +void +ledit_window_set_scroll_pos(ledit_window *window, long pos) { + window->scroll_offset = pos; + window->common->redraw = 1; +} + +void +ledit_window_set_scroll_callback(ledit_window *window, void (*cb)(void *, long), void *data) { + window->scroll_callback = cb; + window->scroll_cb_data = data; +} + +void +ledit_window_set_paste_callback(ledit_window *window, void (*cb)(void *, char *, int), void *data) { + window->paste_callback = cb; + window->paste_cb_data = data; +} + +void +ledit_window_set_button_callback(ledit_window *window, void (*cb)(void *, XEvent *), void *data) { + window->button_callback = cb; + window->button_cb_data = data; +} + +/* FIXME: Change naming convention to fit the rest of ledit */ +/* FIXME: It's a bit weird when an input box pops up during normal mode. + Can/should this be disabled? */ +int ximopen(ledit_window *window, Display *dpy); +void ximinstantiate(Display *dpy, XPointer client, XPointer call); +void ximdestroy(XIM xim, XPointer client, XPointer call); +int xicdestroy(XIC xim, XPointer client, XPointer call); + +/* blatantly stolen from st */ +int +ximopen(ledit_window *window, Display *dpy) +{ + XIMCallback imdestroy = { .client_data = (XPointer)window, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = (XPointer)window, .callback = xicdestroy }; + + window->xim = XOpenIM(dpy, NULL, NULL, NULL); + if (window->xim == NULL) + return 0; + + if (XSetIMValues(window->xim, XNDestroyCallback, &imdestroy, NULL)) { + fprintf( + stderr, "XSetIMValues: Could not set XNDestroyCallback.\n" + ); + } + + window->spotlist = XVaCreateNestedList(0, XNSpotLocation, &window->spot, NULL); + + if (window->xic == NULL) { + window->xic = XCreateIC( + window->xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, window->xwin, + XNDestroyCallback, &icdestroy, NULL + ); + } + if (window->xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + (void)call; + ledit_window *window = (ledit_window *)client; + if (ximopen(window, dpy)) { + XUnregisterIMInstantiateCallback( + dpy, NULL, NULL, NULL, ximinstantiate, (XPointer)window + ); + } +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + (void)xim; + (void)call; + ledit_window *window = (ledit_window *)client; + window->xim = NULL; + XRegisterIMInstantiateCallback( + window->common->dpy, NULL, NULL, NULL, ximinstantiate, (XPointer)window + ); + XFree(window->spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + (void)xim; + (void)call; + ledit_window *window = (ledit_window *)client; + window->xic = NULL; + return 1; +} + +void +xximspot(ledit_window *window, int x, int y) { + if (window->xic == NULL) + return; + /* FIXME! */ + window->spot.x = x; + window->spot.y = y; + XSetICValues(window->xic, XNPreeditAttributes, window->spotlist, NULL); +} + +ledit_window * +ledit_window_create(ledit_common *common, ledit_theme *theme) { + XSetWindowAttributes attrs; + XGCValues gcv; + + ledit_window *window = ledit_malloc(sizeof(ledit_window)); + + window->scroll_dragging = 0; + window->scroll_grab_handle = 0; + window->w = 500; + window->h = 500; + + memset(&window->wattrs, 0, sizeof(attrs)); + window->wattrs.background_pixel = BlackPixel(common->dpy, common->screen); + window->wattrs.colormap = common->cm; + /* this causes the window contents to be kept + * when it is resized, leading to less flicker */ + window->wattrs.bit_gravity = NorthWestGravity; + /* FIXME: FocusChangeMask? */ + window->wattrs.event_mask = KeyPressMask | + ExposureMask | VisibilityChangeMask | StructureNotifyMask | + ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + window->xwin = XCreateWindow( + common->dpy, DefaultRootWindow(common->dpy), 0, 0, + window->w, window->h, 0, common->depth, + InputOutput, common->vis, + CWBackPixel | CWColormap | CWBitGravity | CWEventMask, &window->wattrs + ); + XSetStandardProperties(common->dpy, window->xwin, "ledit", NULL, None, NULL, 0, NULL); + + window->back_buf = XdbeAllocateBackBufferName( + common->dpy, window->xwin, XdbeBackground + ); + window->drawable = window->back_buf; + + memset(&gcv, 0, sizeof(gcv)); + gcv.line_width = 1; + window->gc = XCreateGC(common->dpy, window->back_buf, GCLineWidth, &gcv); + + /* FIXME: move to common */ + window->fontmap = pango_xft_get_font_map(common->dpy, common->screen); + window->context = pango_font_map_create_context(window->fontmap); + + /* FIXME: theme font name */ + window->font = pango_font_description_from_string("Monospace"); + pango_font_description_set_size(window->font, theme->text_size * PANGO_SCALE); + + window->wm_delete_msg = XInternAtom(common->dpy, "WM_DELETE_WINDOW", False); + XSetWMProtocols(common->dpy, window->xwin, &window->wm_delete_msg, 1); + + window->common = common; + window->theme = theme; + + /* FIXME: theme font */ + window->bb = ledit_malloc(sizeof(bottom_bar)); + window->bb->mode = pango_layout_new(window->context); + pango_layout_set_font_description(window->bb->mode, window->font); + /* FIXME: only create "dummy draw" at first and create with proper size when needed */ + window->bb->mode_draw = ledit_draw_create(window, 10, 10); + window->bb->line = pango_layout_new(window->context); + pango_layout_set_font_description(window->bb->line, window->font); + window->bb->line_draw = ledit_draw_create(window, 10, 10); + window->bb->line_w = window->bb->line_h = 10; + window->bb->line_text = NULL; + window->bb->line_alloc = window->bb->line_len = 0; + window->bb->line_cur_pos = 0; + window->bottom_text_shown = 0; + window->message_shown = 0; + + window->xim = NULL; + window->xic = NULL; + if (!ximopen(window, common->dpy)) { + XRegisterIMInstantiateCallback( + common->dpy, NULL, NULL, NULL, + ximinstantiate, (XPointer)window + ); + } + + XMapWindow(common->dpy, window->xwin); + + /* FIXME: why is this part of the window? */ + window->xtarget = XInternAtom(common->dpy, "UTF8_STRING", 0); + window->paste_callback = NULL; + if (window->xtarget == None) + window->xtarget = XA_STRING; + + /* + ledit_clear_window(window); + ledit_redraw_window(window); + */ + if (xsel.primary == NULL) + xsel.primary = txtbuf_new(); + /* FIXME: maybe delay this (i.e. move to different function)? */ + XMapWindow(common->dpy, window->xwin); + + return window; +} + +void +ledit_window_destroy(ledit_window *window) { + /* FIXME: cleanup everything else */ + XDestroyWindow(window->common->dpy, window->xwin); +} + +void +ledit_window_clear(ledit_window *window) { + XSetForeground(window->common->dpy, window->gc, window->theme->text_bg.pixel); + XFillRectangle( + window->common->dpy, window->back_buf, window->gc, 0, 0, window->w, window->h + ); +} + +void +ledit_window_redraw(ledit_window *window) { + ledit_theme *t = window->theme; + if (window->scroll_max > window->text_h) { + XSetForeground(window->common->dpy, window->gc, t->scrollbar_bg.pixel); + XFillRectangle( + window->common->dpy, window->drawable, window->gc, + window->w - t->scrollbar_width, 0, t->scrollbar_width, window->text_h + ); + XSetForeground(window->common->dpy, window->gc, t->text_fg.pixel); + double scroll_h, scroll_y; + get_scroll_pos_height(window, &scroll_y, &scroll_h); + XFillRectangle( + window->common->dpy, window->drawable, window->gc, + window->w - t->scrollbar_width, (int)round(scroll_y), + t->scrollbar_width, (int)round(scroll_h) + ); + } + XSetForeground(window->common->dpy, window->gc, t->text_bg.pixel); + /* FIXME: allow different color for bar */ + XFillRectangle( + window->common->dpy, window->drawable, window->gc, + 0, window->text_h, + window->w, window->h - window->text_h + ); + if (window->bottom_text_shown) { + /* move input method position to cursor */ + PangoRectangle strong, weak; + pango_layout_get_cursor_pos( + window->bb->line, window->bb->line_cur_pos, &strong, &weak + ); + /* FIXME: Is this the best position? The bottom of the lins is + just the bottom of the window, so an input method box will + have to be moved out of the way anyways (fcitx just moves it + up a bit so it sort of works) */ + xximspot(window, strong.x / PANGO_SCALE, window->h); + } + if (window->bottom_text_shown || window->message_shown) { + XCopyArea( + window->common->dpy, window->bb->line_draw->pixmap, + window->drawable, window->gc, + 0, 0, window->bb->line_w, window->bb->line_h, + 0, window->text_h + ); + } else { + XCopyArea( + window->common->dpy, window->bb->mode_draw->pixmap, + window->drawable, window->gc, + 0, 0, window->bb->mode_w, window->bb->mode_h, + window->w - window->bb->mode_w, window->text_h + ); + } + + XdbeSwapInfo swap_info; + swap_info.swap_window = window->xwin; + swap_info.swap_action = XdbeBackground; + + if (!XdbeSwapBuffers(window->common->dpy, &swap_info, 1)) + exit(1); + XFlush(window->common->dpy); +} + +void +ledit_window_get_textview_size(ledit_window *window, int *w_ret, int *h_ret) { + *w_ret = window->text_w; + *h_ret = window->text_h; +} + +void +ledit_window_resize(ledit_window *window, int w, int h) { + window->w = w; + window->h = h; + recalc_text_size(window); + window->common->redraw = 1; +} + +void +clipboard_primary_to_clipboard(ledit_window *window) { + Atom clipboard; + + free(xsel.clipboard); + xsel.clipboard = NULL; + + /* FIXME: don't copy if text empty (no selection)? */ + if (xsel.primary->text != NULL) { + xsel.clipboard = ledit_strdup(xsel.primary->text); + clipboard = XInternAtom(window->common->dpy, "CLIPBOARD", 0); + XSetSelectionOwner(window->common->dpy, clipboard, window->xwin, CurrentTime); + } +} + +void +clipboard_paste_clipboard(ledit_window *window) { + Atom clipboard; + + clipboard = XInternAtom(window->common->dpy, "CLIPBOARD", 0); + XConvertSelection(window->common->dpy, clipboard, window->xtarget, clipboard, window->xwin, CurrentTime); +} + +void +clipboard_paste_primary(ledit_window *window) { + XConvertSelection(window->common->dpy, XA_PRIMARY, window->xtarget, XA_PRIMARY, window->xwin, CurrentTime); +} + +static void +clipboard_propnotify(ledit_window *window, XEvent *e) { + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(window->common->dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + clipboard_selnotify(window, e); + } +} + +static void +clipboard_selnotify(ledit_window *window, XEvent *e) { + unsigned long nitems, ofs, rem; + int format; + unsigned char *data; + Atom type, incratom, property = None; + + incratom = XInternAtom(window->common->dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) { + property = e->xselection.property; + } else if (e->type == PropertyNotify) { + property = e->xproperty.atom; + } + + if (property == None) + return; + + do { + /* FIXME: proper error logging */ + if (XGetWindowProperty(window->common->dpy, window->xwin, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + if (e->type == PropertyNotify && nitems == 0 && rem == 0) { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(window->wattrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(window->common->dpy, window->xwin, CWEventMask, &window->wattrs); + /* remove callback - this has to be set again the next time + something is pasted */ + window->paste_callback = NULL; + window->paste_cb_data = NULL; + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner sends us the next + * chunk of data. + */ + MODBIT(window->wattrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(window->common->dpy, window->xwin, CWEventMask, &window->wattrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(window->common->dpy, window->xwin, (int)property); + continue; + } + + /* FIXME: XGetWindowProperty takes data as unsigned char, so it is casted here. Why? */ + if (window->paste_callback) + window->paste_callback(window->paste_cb_data, (char *)data, (int)(nitems * format / 8)); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(window->common->dpy, window->xwin, (int)property); +} + +static void +clipboard_selrequest(ledit_window *window, XEvent *e) +{ + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(window->common->dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = window->xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (unsigned char *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == window->xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(window->common->dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = xsel.primary->text; + } else if (xsre->selection == clipboard) { + seltext = xsel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (unsigned char *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +/* FIXME: improve set_scroll_pos; make it a bit clearer */ +int +ledit_window_button_press(ledit_window *window, XEvent *event) { + int x, y; + double scroll_h, scroll_y; + switch (event->xbutton.button) { + case Button1: + get_scroll_pos_height(window, &scroll_y, &scroll_h); + x = event->xbutton.x; + y = event->xbutton.y; + if (x >= window->text_w) { + window->scroll_dragging = 1; + window->scroll_grab_handle = y; + if (y < scroll_y || y > scroll_y + scroll_h) { + double new_scroll_y = y - scroll_h / 2; + set_scroll_pos(window, new_scroll_y); + } + return 1; + } else if (y < window->text_h) { + if (window->button_callback) + window->button_callback(window->button_cb_data, event); + return 1; + } + break; + case Button4: + window->scroll_offset -= window->theme->scrollbar_step; + if (window->scroll_offset < 0) + window->scroll_offset = 0; + if (window->scroll_callback) + window->scroll_callback(window->scroll_cb_data, (long)window->scroll_offset); + return 1; + case Button5: + if (window->scroll_offset + window->text_h < window->scroll_max) { + window->scroll_offset += window->theme->scrollbar_step; + if (window->scroll_offset + window->text_h > window->scroll_max) { + window->scroll_offset = window->scroll_max - window->text_h; + } + } + if (window->scroll_callback) + window->scroll_callback(window->scroll_cb_data, (long)window->scroll_offset); + return 1; + } + return 0; +} + +int +ledit_window_button_release(ledit_window *window, XEvent *event) { + if (event->xbutton.button == Button1) { + window->scroll_dragging = 0; + if (window->button_callback) + window->button_callback(window->button_cb_data, event); + return 1; + } + return 0; +} + +int +ledit_window_drag_motion(ledit_window *window, XEvent *event) { + if (window->scroll_dragging) { + double scroll_h, scroll_y; + get_scroll_pos_height(window, &scroll_y, &scroll_h); + scroll_y += event->xbutton.y - window->scroll_grab_handle; + window->scroll_grab_handle = event->xbutton.y; + set_scroll_pos(window, scroll_y); + return 1; + } else { + if (window->button_callback) + window->button_callback(window->button_cb_data, event); + return 1; + } + return 0; +} + +int +ledit_window_clipboard_event(ledit_window *window, XEvent *event) { + /* FIXME: paste in bottom bar */ + switch (event->type) { + case SelectionNotify: + clipboard_selnotify(window, event); + break; + case PropertyNotify: + clipboard_propnotify(window, event); + break; + case SelectionRequest: + clipboard_selrequest(window, event); + break; + default: + break; + } + return 0; +} diff --git a/window.h b/window.h @@ -0,0 +1,80 @@ +typedef struct bottom_bar bottom_bar; + +typedef struct { + PangoFontMap *fontmap; + PangoContext *context; + PangoFontDescription *font; + GC gc; + Window xwin; + XdbeBackBuffer back_buf; + Drawable drawable; + int w; + int h; + int text_w; + int text_h; + int scroll_dragging; + int scroll_grab_handle; + long scroll_max; + double scroll_offset; /* double for smoother scrolling */ + int bottom_text_shown; + int message_shown; + XSetWindowAttributes wattrs; + Atom wm_delete_msg; + Atom xtarget; + bottom_bar *bb; + int bb_msg_shown; + + /* IME stuff */ + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + + ledit_common *common; + ledit_theme *theme; + void (*paste_callback)(void *, char *, int); + void (*scroll_callback)(void *, long); + void (*button_callback)(void *, XEvent *); + void *paste_cb_data; + void *scroll_cb_data; + void *button_cb_data; +} ledit_window; + +ledit_window *ledit_window_create(ledit_common *common, ledit_theme *theme); +void ledit_window_destroy(ledit_window *window); + +/* FIXME: this is a bit confusing because there's a difference between editable + text shown and non-editable message shown */ +void ledit_window_set_bottom_bar_text_shown(ledit_window *window, int shown); +int ledit_window_bottom_bar_text_shown(ledit_window *window); +void ledit_window_set_bottom_bar_cursor(ledit_window *window, int byte_pos); +int ledit_window_get_bottom_bar_cursor(ledit_window *window); +void ledit_window_insert_bottom_bar_text(ledit_window *window, char *text, int len); +void ledit_window_set_bottom_bar_text(ledit_window *window, char *text, int len); +char *ledit_window_get_bottom_bar_text(ledit_window *window); +void ledit_window_show_message(ledit_window *window, char *text, int len); +void ledit_window_hide_message(ledit_window *window); +int ledit_window_message_shown(ledit_window *window); +void ledit_window_set_mode(ledit_window *window, enum ledit_mode mode); + +void ledit_window_set_scroll_max(ledit_window *window, long max); +void ledit_window_set_scroll_pos(ledit_window *window, long pos); +void ledit_window_set_scroll_callback(ledit_window *window, void (*cb)(void *, long), void *data); +void ledit_window_set_paste_callback(ledit_window *window, void (*cb)(void *, char *, int), void *data); +void ledit_window_set_button_callback(ledit_window *window, void (*cb)(void *, XEvent *), void *data); + +void ledit_window_clear(ledit_window *window); +void ledit_window_redraw(ledit_window *window); +void ledit_window_resize(ledit_window *window, int w, int h); +void ledit_window_get_textview_size(ledit_window *window, int *w_ret, int *h_ret); + +void clipboard_primary_to_clipboard(ledit_window *window); +void clipboard_paste_clipboard(ledit_window *window); +void clipboard_paste_primary(ledit_window *window); +txtbuf *ledit_window_get_primary_clipboard_buffer(void); + +int ledit_window_button_press(ledit_window *window, XEvent *event); +int ledit_window_button_release(ledit_window *window, XEvent *event); +int ledit_window_drag_motion(ledit_window *window, XEvent *event); +int ledit_window_clipboard_event(ledit_window *window, XEvent *event); +void xximspot(ledit_window *window, int x, int y);