URI: 
       tImplement switching between hard line and soft line mode - ledit - Text editor (WIP)
  HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ledit.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit f9064673e6ced9b9d8a2c1461c108170c15a96bc
   DIR parent f46f28e4d372daa0353489d65a29078e69fe0376
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Sun, 14 Nov 2021 21:50:14 +0100
       
       Implement switching between hard line and soft line mode
       
       But it's all really ugly and buggy.
       
       Diffstat:
         M buffer.c                            |      85 +++++++++++++++++++++++++++++--
         M buffer.h                            |      10 ++++++++--
         M keys_basic.c                        |     291 ++++++++++++++++++++-----------
         M keys_basic.h                        |       1 +
         M keys_basic_config.h                 |       3 +++
         M ledit.c                             |       1 +
         M memory.c                            |      17 +++++++++++++++++
         M memory.h                            |       1 +
         M window.c                            |      21 +++++++++++++++++----
         M window.h                            |       2 ++
       
       10 files changed, 322 insertions(+), 110 deletions(-)
       ---
   DIR diff --git a/buffer.c b/buffer.c
       t@@ -1,5 +1,6 @@
        /* FIXME: shrink buffers when text length less than a fourth of the size */
        /* FIXME: also cache PangoLayouts since keeping them around isn't really of much use? */
       +/* FIXME: handle all undo within buffer to keep it consistent */
        
        #include <stdio.h>
        #include <errno.h>
       t@@ -1404,13 +1405,13 @@ ledit_buffer_get_legal_normal_pos(ledit_buffer *buffer, int line, int pos) {
        
        void
        ledit_buffer_delete_range(
       -    ledit_buffer *buffer, int line_based,
       +    ledit_buffer *buffer, enum delete_mode delmode,
            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, txtbuf *text_ret) {
                ledit_buffer_delete_range_base(
       -            buffer, line_based,
       +            buffer, delmode,
                    line_index1, byte_index1,
                    line_index2, byte_index2,
                    new_line_ret, new_byte_ret,
       t@@ -1423,10 +1424,15 @@ ledit_buffer_delete_range(
        }
        
        /* Note: line_index* and byte_index* don't need to be sorted */
       +/* line_index1, byte_index1 are used as the cursor position in order
       +   to determine the new cursor position */
        /* FIXME: use at least somewhat sensible variable names */
       +/* FIXME: I once noticed a bug where using 'dG' to delete to the end of
       +   the file caused a line index way larger than buffer->lines_num to be
       +   given, but I couldn't reproduce this bug */
        void
        ledit_buffer_delete_range_base(
       -    ledit_buffer *buffer, int line_based,
       +    ledit_buffer *buffer, enum delete_mode delmode,
            int line_index1, int byte_index1,
            int line_index2, int byte_index2,
            int *new_line_ret, int *new_byte_ret,
       t@@ -1435,9 +1441,80 @@ ledit_buffer_delete_range_base(
                /* range line x, range byte x */
                int rgl1 = 0, rgb1 = 0, rgl2 = 0, rgb2 = 0;
                int new_line = 0, new_byte = 0;
       +        assert(line_index1 >= 0);
       +        assert(line_index2 >= 0);
       +        assert(line_index1 < buffer->lines_num);
       +        assert(line_index2 < buffer->lines_num);
                /* FIXME: could this be simplified by just calculating the range and then using
                   the non-line-based version? */
       -        if (line_based) {
       +        if (delmode == DELETE_HARDLINE) {
       +                int x, sl_useless;
       +                int l1 = line_index1, l2 = line_index2;
       +                if (line_index1 > line_index2) {
       +                        l1 = line_index2;
       +                        l2 = line_index1;
       +                }
       +                int dell1 = l1, dell2 = l2;
       +                ledit_line *ll = ledit_buffer_get_line(buffer, line_index1);
       +                ledit_pos_to_x_softline(ll, byte_index1, &x, &sl_useless);
       +                if (l1 > 0 && l2 < buffer->lines_num - 1) {
       +                        rgl1 = l1;
       +                        rgb1 = 0;
       +                        rgl2 = l2 + 1;
       +                        rgb2 = 0;
       +                } else if (l1 > 0) {
       +                        rgl1 = l1 - 1;
       +                        ll = ledit_buffer_get_line(buffer, rgl1);
       +                        rgb1 = ll->len;
       +                        rgl2 = l2;
       +                        ll = ledit_buffer_get_line(buffer, rgl2);
       +                        rgb2 = ll->len;
       +                } else if (l2 < buffer->lines_num - 1) {
       +                        rgl1 = l1;
       +                        rgb1 = 0;
       +                        rgl2 = l2 + 1;
       +                        rgb2 = 0;
       +                } else {
       +                        rgl1 = l1;
       +                        rgb1 = 0;
       +                        rgl2 = l2;
       +                        ll = ledit_buffer_get_line(buffer, rgl2);
       +                        rgb2 = ll->len;
       +                }
       +                if (text_ret) {
       +                        ledit_buffer_copy_text_to_txtbuf(
       +                            buffer, text_ret,
       +                            rgl1, rgb1, rgl2, rgb2
       +                        );
       +                }
       +                /* default is dell1 = l1, dell2 = l2 */
       +                if (l2 < buffer->lines_num - 1) {
       +                        new_line = l1;
       +                        ledit_x_softline_to_pos(
       +                            ledit_buffer_get_line(buffer, l2 + 1),
       +                            x, 0, &new_byte
       +                        );
       +                } else if (l1 > 0) {
       +                        new_line = l1 - 1;
       +                        ledit_x_softline_to_pos(
       +                            ledit_buffer_get_line(buffer, l1 - 1),
       +                            x, 0, &new_byte
       +                        );
       +                } else {
       +                        dell1 = l1 + 1;
       +                        dell2 = l2;
       +                        new_line = l1;
       +                        new_byte = 0;
       +                        /* happens when all lines are deleted, so one line has to be cleared */
       +                        ll = ledit_buffer_get_line(buffer, l1);
       +                        delete_line_section_base(
       +                            buffer, l1, 0, ll->len
       +                        );
       +                }
       +                if (dell1 <= dell2) {
       +                        ledit_buffer_delete_line_entries_base(buffer, dell1, dell2);
       +                }
       +        } else if (delmode == DELETE_SOFTLINE) {
                        int x, softline1, softline2;
                        ledit_line *line1 = ledit_buffer_get_line(buffer, line_index1);
                        normalize_and_set_pango_text(line1);
   DIR diff --git a/buffer.h b/buffer.h
       t@@ -53,6 +53,12 @@ struct ledit_buffer {
                ledit_buffer_marklist *marklist;
        };
        
       +enum delete_mode {
       +        DELETE_CHAR,
       +        DELETE_SOFTLINE,
       +        DELETE_HARDLINE
       +};
       +
        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);
       t@@ -110,7 +116,7 @@ void ledit_buffer_delete_line_entries_base(ledit_buffer *buffer, int index1, int
        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,
       +    ledit_buffer *buffer, enum delete_mode delmode,
            int line_index1, int byte_index1,
            int line_index2, int byte_index2,
            int *new_line_ret, int *new_byte_ret,
       t@@ -135,7 +141,7 @@ void ledit_buffer_delete_line_entries(ledit_buffer *buffer, int index1, int inde
        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,
       +    ledit_buffer *buffer, enum delete_mode delmode,
            int line_index1, int byte_index1,
            int line_index2, int byte_index2,
            int *new_line_ret, int *new_byte_ret,
   DIR diff --git a/keys_basic.c b/keys_basic.c
       t@@ -78,6 +78,7 @@ static struct {
        } key_stack = {0, 0, NULL};
        
        static struct action (*grab_char_cb)(ledit_buffer *buffer, char *text, int len) = NULL;
       +static int hard_line_based = 1;
        
        void
        basic_key_cleanup(void) {
       t@@ -322,62 +323,72 @@ finalize_repetition_stack(void) {
                repetition_stack.tmp_stack = tmpstack;
        }
        
       -/* get the new line and softline when moving 'movement' softlines up or
       +/* get the new line and softline when moving 'movement' softlines
       +   (or hardlines if hard_line_based is set) 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;
       +        if (hard_line_based) {
       +                *new_line_ret = cur_line + movement;
       +                if (*new_line_ret < 0)
       +                        *new_line_ret = 0;
       +                else if (*new_line_ret >= buffer->lines_num)
       +                        *new_line_ret = buffer->lines_num - 1;
       +                *new_softline_ret = 0;
       +        } else {
       +                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 {
       -                                *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;
       +                                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;
                                }
       -                        endline++;
       -                        if (movement >= 0) {
       -                                *new_softline_ret = movement;
       +                } else if (movement < 0) {
       +                        int softlines = 0;
       +                        if (softline + movement >= 0) {
       +                                *new_line_ret = cur_line;
       +                                *new_softline_ret = softline + movement;
                                } else {
       -                                *new_softline_ret = 0;
       +                                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;
                                }
       -                        *new_line_ret = endline;
       +                } else {
       +                        *new_line_ret = cur_line;
       +                        *new_softline_ret = softline;
                        }
       -        } else {
       -                *new_line_ret = cur_line;
       -                *new_softline_ret = softline;
                }
        }
        
       t@@ -396,8 +407,15 @@ delete_range(
                ledit_range cur_range, del_range;
                cur_range.line1 = buffer->cur_line;
                cur_range.byte1 = buffer->cur_index;
       +        enum delete_mode delmode = DELETE_CHAR;
       +        if (line_based) {
       +                if (hard_line_based)
       +                        delmode = DELETE_HARDLINE;
       +                else
       +                        delmode = DELETE_SOFTLINE;
       +        }
                ledit_buffer_delete_range(
       -            buffer, line_based,
       +            buffer, delmode,
                    line_index1, byte_index1,
                    line_index2, byte_index2,
                    &buffer->cur_line, &buffer->cur_index,
       t@@ -544,8 +562,10 @@ append_line_above(ledit_buffer *buffer, char *text, int len) {
                /* do this here already so the mode group is the same for the newline insertion */
                enter_insert(buffer, text, len);
                ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
       +        /* FIXME: this is more "elegant", but inefficient because this doesn't
       +           actually need to be called when hard_line_based == 1 */
                pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
       -        if (sli == 0) {
       +        if (hard_line_based || sli == 0) {
                        insert_text(buffer, buffer->cur_line, 0, "\n", -1, -1, -1, buffer->cur_line, 0, 1);
                } else {
                        PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, sli);
       t@@ -561,7 +581,7 @@ append_line_below(ledit_buffer *buffer, char *text, int len) {
                ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
                pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
                PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, sli);
       -        if (sl->start_index + sl->length == ll->len) {
       +        if (hard_line_based || sl->start_index + sl->length == ll->len) {
                        insert_text(buffer, buffer->cur_line, ll->len, "\n", -1, -1, -1, buffer->cur_line + 1, 0, 1);
                } else {
                        insert_text(buffer, buffer->cur_line, sl->start_index + sl->length, "\n\n", -1, -1, -1, buffer->cur_line + 1, 0, 1);
       t@@ -587,9 +607,13 @@ append_after_eol(ledit_buffer *buffer, char *text, int len) {
                /* make cursor jump back to original position on undo */
                push_undo_empty_insert(buffer, buffer->cur_line, buffer->cur_index, 1);
                ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
       -        pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
       -        PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, sli);
       -        buffer->cur_index = sl->start_index + sl->length;
       +        if (hard_line_based) {
       +                buffer->cur_index = ll->len;
       +        } else {
       +                pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
       +                PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, sli);
       +                buffer->cur_index = sl->start_index + sl->length;
       +        }
                return (struct action){ACTION_NONE, NULL};
        }
        
       t@@ -629,6 +653,7 @@ move_to_line(ledit_buffer *buffer, char *text, int len) {
                return (struct action){ACTION_NONE, NULL};
        }
        
       +/* FIXME: should these scrolling functions change behavior when hard_line_based == 1? */
        static void
        scroll_lines(ledit_buffer *buffer, int lines, int dir) {
                int final_lines;
       t@@ -802,20 +827,21 @@ static struct action
        delete_to_eol(ledit_buffer *buffer, char *text, int len) {
                (void)text;
                (void)len;
       -        /* FIXME: move to separate function */
       -        if (!key_stack_empty()) {
       -                clear_key_stack();
       -                ledit_window_show_message(buffer->window, "Invalid key", -1);
       -                return (struct action){ACTION_NONE, NULL};
       -        }
       -        int x, sli;
       +        if (!key_stack_empty())
       +                return err_invalid_key(buffer);
       +        int end, x, sli;
                ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
       -        pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
       -        PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, sli);
       +        if (hard_line_based) {
       +                end = ll->len;
       +        } else {
       +                pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
       +                PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, sli);
       +                end = sl->start_index + sl->length;
       +        }
                delete_range(
                    buffer, 0, 0,
                    buffer->cur_line, buffer->cur_index,
       -            buffer->cur_line, sl->start_index + sl->length, 1
       +            buffer->cur_line, end, 1
                );
                paste_buffer_line_based = 0;
                buffer->cur_index = ledit_buffer_get_legal_normal_pos(
       t@@ -829,20 +855,22 @@ static struct action
        change_to_eol(ledit_buffer *buffer, char *text, int len) {
                (void)text;
                (void)len;
       -        if (!key_stack_empty()) {
       -                clear_key_stack();
       -                ledit_window_show_message(buffer->window, "Invalid key", -1);
       -                return (struct action){ACTION_NONE, NULL};
       -        }
       +        if (!key_stack_empty())
       +                return err_invalid_key(buffer);
                ledit_buffer_set_mode(buffer, INSERT);
       -        int x, sli;
       +        int end, x, sli;
                ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
       -        pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
       -        PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, sli);
       +        if (hard_line_based) {
       +                end = ll->len;
       +        } else {
       +                pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
       +                PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, sli);
       +                end = sl->start_index + sl->length;
       +        }
                delete_range(
                    buffer, 0, 0,
                    buffer->cur_line, buffer->cur_index,
       -            buffer->cur_line, sl->start_index + sl->length, 1
       +            buffer->cur_line, end, 1
                );
                paste_buffer_line_based = 0;
                ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line);
       t@@ -896,7 +924,7 @@ change_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type) {
                /* this hackery is needed to avoid deleting the entire last line and
                   instead leave an empty line - this should be made nicer (FIXME) */
                int pos1 = buffer->cur_index, pos2 = char_pos, x, sli;
       -        if (line_based) {
       +        if (line_based && !hard_line_based) {
                        ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
                        pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
                        PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, sli);
       t@@ -905,6 +933,10 @@ change_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type) {
                        pango_layout_index_to_line_x(ll->layout, char_pos, 0, &sli, &x);
                        sl = pango_layout_get_line_readonly(ll->layout, sli);
                        pos2 = sl->start_index + sl->length;
       +        } else if (line_based && hard_line_based) {
       +                pos1 = 0;
       +                ledit_line *ll = ledit_buffer_get_line(buffer, line);
       +                pos2 = ll->len;
                }
                /* force line_based to 0 (see comment about hackery above) */
                delete_range(
       t@@ -989,6 +1021,9 @@ yank_lines(ledit_buffer *buffer, char *text, int len) {
                return (struct action){ACTION_NONE, NULL};
        }
        
       +/* FIXME: delete_range and yank put different things in past_buffer - yank doesn't include
       +   extra newlines at the beginning and end (this doesn't really matter because paste
       +   ignores them anyways, but it is a bit weird) */
        static void
        yank_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type) {
                int line_based = type == KEY_MOTION_LINE ? 1 : 0;
       t@@ -999,7 +1034,7 @@ yank_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type) {
                        swap(&l1, &l2);
                        swap(&b1, &b2);
                }
       -        if (line_based) {
       +        if (line_based && !hard_line_based) {
                        int x, sl1, sl2;
                        ledit_line *ll1 = ledit_buffer_get_line(buffer, l1);
                        pango_layout_index_to_line_x(ll1->layout, b1, 0, &sl1, &x);
       t@@ -1011,6 +1046,11 @@ yank_cb(ledit_buffer *buffer, int line, int char_pos, enum key_type type) {
                        ledit_buffer_copy_text_to_txtbuf(
                            buffer, paste_buffer, l1, pl1->start_index, l2, pl2->start_index + pl2->length
                        );
       +        } else if (line_based && hard_line_based) {
       +                ledit_line *ll = ledit_buffer_get_line(buffer, l2);
       +                ledit_buffer_copy_text_to_txtbuf(
       +                    buffer, paste_buffer, l1, 0, l2, ll->len
       +                );
                } else {
                        ledit_buffer_copy_text_to_txtbuf(
                            buffer, paste_buffer, l1, b1, l2, b2
       t@@ -1091,10 +1131,16 @@ paste_normal(ledit_buffer *buffer, char *text, int len) {
                        int x, softline;
                        ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line);
                        ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
       -                pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &softline, &x);
       -                PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, softline);
       +                int brk = 0;
       +                if (hard_line_based) {
       +                        brk = ll->len;
       +                } else {
       +                        pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &softline, &x);
       +                        PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, softline);
       +                        brk = sl->start_index + sl->length;
       +                }
                        insert_text(
       -                    buffer, buffer->cur_line, sl->start_index + sl->length,
       +                    buffer, buffer->cur_line, brk,
                            "\n", -1, -1, -1, buffer->cur_line, buffer->cur_index, 1
                        );
                        int text_len = paste_buffer->len;
       t@@ -1143,10 +1189,14 @@ paste_normal_backwards(ledit_buffer *buffer, char *text, int len) {
                        int x, softline;
                        ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line);
                        ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
       -                pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &softline, &x);
       -                PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, softline);
       +                int brk = 0;
       +                if (!hard_line_based) {
       +                        pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &softline, &x);
       +                        PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, softline);
       +                        brk = sl->start_index;
       +                }
                        insert_text(
       -                    buffer, buffer->cur_line, sl->start_index,
       +                    buffer, buffer->cur_line, brk,
                            "\n", -1, -1, -1, buffer->cur_line, buffer->cur_index, 1
                        );
                        int text_len = paste_buffer->len;
       t@@ -1341,8 +1391,11 @@ move_to_eol(ledit_buffer *buffer, char *text, int len) {
                    &new_line, &new_softline
                );
                ledit_line *ll = ledit_buffer_get_line(buffer, new_line);
       -        PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, new_softline);
       -        int end_index = sl->start_index + sl->length;
       +        int end_index = ll->len;
       +        if (!hard_line_based) {
       +                PangoLayoutLine *sl = pango_layout_get_line_readonly(ll->layout, new_softline);
       +                end_index = sl->start_index + sl->length;
       +        }
                if (cb != NULL) {
                        cb(buffer, new_line, end_index, KEY_MOTION_CHAR);
                } else {
       t@@ -1671,7 +1724,7 @@ join_lines(ledit_buffer *buffer, char *text, int len) {
                        oldlen = ll1->len;
                        /* FIXME: truncate whitespace to one space */
                        ledit_buffer_delete_range(
       -                    buffer, 0,
       +                    buffer, DELETE_CHAR,
                            cur_line, ll1->len, cur_line + 1, 0,
                            NULL, NULL, &del_range, buf
                        );
       t@@ -1697,12 +1750,16 @@ insert_at_beginning(ledit_buffer *buffer, char *text, int len) {
                if (!key_stack_empty())
                        return err_invalid_key(buffer);
                enter_insert(buffer, text, len);
       -        int x, sli;
       -        ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
       -        pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
       -        PangoLayoutLine *pl = pango_layout_get_line_readonly(ll->layout, sli);
       +        int new_index = 0;
       +        if (!hard_line_based) {
       +                int x, sli;
       +                ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
       +                pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
       +                PangoLayoutLine *pl = pango_layout_get_line_readonly(ll->layout, sli);
       +                new_index = pl->start_index;
       +        }
                push_undo_empty_insert(buffer, buffer->cur_line, buffer->cur_index, 1);
       -        buffer->cur_index = pl->start_index;
       +        buffer->cur_index = new_index;
                ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line);
                return (struct action){ACTION_NONE, NULL};
        }
       t@@ -1711,20 +1768,25 @@ static struct action
        cursor_to_first_non_ws(ledit_buffer *buffer, char *text, int len) {
                (void)text;
                (void)len;
       -        int x, sli;
                motion_callback cb;
                int num = get_key_repeat_and_motion_cb(&cb);
                if (num != 0)
                        return err_invalid_key(buffer);
       +        int new_index = 0;
                ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
       -        pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
       -        PangoLayoutLine *pl = pango_layout_get_line_readonly(ll->layout, sli);
       -        int new_index = ledit_line_next_non_whitespace(ll, pl->start_index);
       -        /* next non-whitespace might be on next softline */
       -        if (new_index >= pl->start_index + pl->length) {
       -                new_index = ledit_buffer_prev_cursor_pos(
       -                    buffer, buffer->cur_line, pl->start_index + pl->length, 1
       -                );
       +        if (hard_line_based) {
       +                new_index = ledit_line_next_non_whitespace(ll, 0);
       +        } else {
       +                int x, sli;
       +                pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
       +                PangoLayoutLine *pl = pango_layout_get_line_readonly(ll->layout, sli);
       +                new_index = ledit_line_next_non_whitespace(ll, pl->start_index);
       +                /* next non-whitespace might be on next softline */
       +                if (new_index >= pl->start_index + pl->length) {
       +                        new_index = ledit_buffer_prev_cursor_pos(
       +                            buffer, buffer->cur_line, pl->start_index + pl->length, 1
       +                        );
       +                }
                }
                if (cb != NULL) {
                        cb(buffer, buffer->cur_line, new_index, KEY_MOTION_CHAR);
       t@@ -1748,13 +1810,17 @@ cursor_to_beginning(ledit_buffer *buffer, char *text, int len) {
                if (num != 0)
                        return err_invalid_key(buffer);
                /* FIXME: should anything be done with num? */
       -        ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
       -        pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
       -        PangoLayoutLine *pl = pango_layout_get_line_readonly(ll->layout, sli);
       +        int start_index = 0;
       +        if (!hard_line_based) {
       +                ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line);
       +                pango_layout_index_to_line_x(ll->layout, buffer->cur_index, 0, &sli, &x);
       +                PangoLayoutLine *pl = pango_layout_get_line_readonly(ll->layout, sli);
       +                start_index = pl->start_index;
       +        }
                if (cb != NULL) {
       -                cb(buffer, buffer->cur_line, pl->start_index, KEY_MOTION_CHAR);
       +                cb(buffer, buffer->cur_line, start_index, KEY_MOTION_CHAR);
                } else {
       -                buffer->cur_index = pl->start_index;
       +                buffer->cur_index = start_index;
                        if (buffer->common->mode == VISUAL) {
                                ledit_buffer_set_selection(
                                    buffer,
       t@@ -2166,6 +2232,31 @@ replace(ledit_buffer *buffer, char *text, int len) {
                return (struct action){ACTION_NONE, NULL};
        }
        
       +static void
       +set_hard_line_based(ledit_buffer *buffer, int hl) {
       +        hard_line_based = hl;
       +        char *text = hl ? "|HL" : "|SL";
       +        ledit_window_set_mode_extra_text(buffer->window, text);
       +}
       +
       +static struct action
       +toggle_hard_line_based(ledit_buffer *buffer, char *text, int len) {
       +        (void)buffer;
       +        (void)text;
       +        (void)len;
       +        int num = get_key_repeat();
       +        if (num != 0)
       +                return err_invalid_key(buffer);
       +        set_hard_line_based(buffer, !hard_line_based);
       +        return (struct action){ACTION_NONE, NULL};
       +}
       +
       +/* FIXME: this is sort of all over the place and ugly */
       +void
       +keys_basic_init(ledit_buffer *buffer) {
       +        set_hard_line_based(buffer, 1);
       +}
       +
        static struct action
        handle_key(ledit_buffer *buffer, char *key_text, int len, KeySym sym, unsigned int key_state, int lang_index, int *found) {
                struct key *cur_keys = keys[lang_index].keys;
   DIR diff --git a/keys_basic.h b/keys_basic.h
       t@@ -1,2 +1,3 @@
       +void keys_basic_init(ledit_buffer *buffer);
        void basic_key_cleanup(void);
        struct action basic_key_handler(ledit_buffer *buffer, XEvent *event, int lang_index);
   DIR diff --git a/keys_basic_config.h b/keys_basic_config.h
       t@@ -1,3 +1,4 @@
       +/* FIXME: these aren't really used properly */
        enum key_type {
                KEY_NONE = 0,
                KEY_MISC = 1,
       t@@ -93,6 +94,7 @@ static struct action replace(ledit_buffer *buffer, char *text, int len);
        static struct action cursor_to_first_non_ws(ledit_buffer *buffer, char *text, int len);
        static struct action join_lines(ledit_buffer *buffer, char *text, int len);
        static struct action insert_at_beginning(ledit_buffer *buffer, char *text, int len);
       +static struct action toggle_hard_line_based(ledit_buffer *buffer, char *text, int len);
        
        /* FIXME: maybe sort these and use binary search
           -> but that would mess with the catch-all keys */
       t@@ -111,6 +113,7 @@ static struct key keys_en[] = {
                {"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},
                {"h",  ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_left},
       +        {"t",  ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &toggle_hard_line_based},
                {NULL,  0, XK_space, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_right},
                {"j",  ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_down},
                {"n",  ControlMask, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION | KEY_NUMBERALLOWED, &cursor_down},
   DIR diff --git a/ledit.c b/ledit.c
       t@@ -292,6 +292,7 @@ setup(int argc, char *argv[]) {
                }
                ledit_buffer_set_mode(buffer, NORMAL);
                ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->cur_index);
       +        keys_basic_init(buffer);
        
                redraw();
        }
   DIR diff --git a/memory.c b/memory.c
       t@@ -47,3 +47,20 @@ ledit_realloc(void *ptr, size_t size) {
                        fatal_err("Out of memory.\n");
                return new_ptr;
        }
       +
       +/* Concatenate the two given strings and return the result.
       +   This allocates new memory for the result string, unlike
       +   the actual strcat. Aborts program on error */
       +char *
       +ledit_strcat(const char *str1, const char *str2) {
       +        int len1, len2;
       +        char *ret;
       +
       +        len1 = strlen(str1);
       +        len2 = strlen(str2);
       +        ret = ledit_malloc(len1 + len2 + 1);
       +        strcpy(ret, str1);
       +        strcpy(ret + len1, str2);
       +
       +        return ret;
       +}
   DIR diff --git a/memory.h b/memory.h
       t@@ -3,3 +3,4 @@ char *ledit_strndup(const char *s, size_t n);
        void *ledit_malloc(size_t size);
        void *ledit_calloc(size_t nmemb, size_t size);
        void *ledit_realloc(void *ptr, size_t size);
       +char *ledit_strcat(const char *str1, const char *str2);
   DIR diff --git a/window.c b/window.c
       t@@ -240,20 +240,24 @@ ledit_window_hide_message(ledit_window *window) {
        
        void
        ledit_window_set_mode(ledit_window *window, enum ledit_mode mode) {
       +        char *text;
                switch (mode) {
                        case NORMAL:
       -                        pango_layout_set_text(window->bb->mode, "Normal", -1);
       +                        text = "Normal";
                                break;
                        case VISUAL:
       -                        pango_layout_set_text(window->bb->mode, "Visual", -1);
       +                        text = "Visual";
                                break;
                        case INSERT:
       -                        pango_layout_set_text(window->bb->mode, "Insert", -1);
       +                        text = "Insert";
                                break;
                        default:
       -                        pango_layout_set_text(window->bb->mode, "ledit is buggy", -1);
       +                        text = "ledit is buggy";
                                break;
                }
       +        char *final_text = ledit_strcat(text, window->mode_extra_text ? window->mode_extra_text : "");
       +        pango_layout_set_text(window->bb->mode, final_text, -1);
       +        free(final_text);
                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);
       t@@ -261,6 +265,12 @@ ledit_window_set_mode(ledit_window *window, enum ledit_mode mode) {
                recalc_text_size(window);
        }
        
       +void
       +ledit_window_set_mode_extra_text(ledit_window *window, char *text) {
       +        window->mode_extra_text = ledit_strdup(text);
       +        ledit_window_set_mode(window, window->common->mode);
       +}
       +
        /* FIXME: give these functions more sensible names */
        static void
        get_scroll_pos_height(ledit_window *window, double *pos, double *height) {
       t@@ -411,6 +421,7 @@ ledit_window_create(ledit_common *common, ledit_theme *theme) {
                window->scroll_grab_handle = 0;
                window->w = 500;
                window->h = 500;
       +        window->mode_extra_text = NULL;
        
                memset(&window->wattrs, 0, sizeof(attrs));
                window->wattrs.background_pixel = BlackPixel(common->dpy, common->screen);
       t@@ -516,6 +527,8 @@ ledit_window_destroy(ledit_window *window) {
                g_object_unref(window->bb->line);
                ledit_draw_destroy(window, window->bb->mode_draw);
                ledit_draw_destroy(window, window->bb->line_draw);
       +        if (window->mode_extra_text)
       +                free(window->mode_extra_text);
                free(window->bb->line_text);
                free(window->bb);
                free(window);
   DIR diff --git a/window.h b/window.h
       t@@ -38,6 +38,7 @@ typedef struct {
                void *paste_cb_data;
                void *scroll_cb_data;
                void *button_cb_data;
       +        char *mode_extra_text;
        } ledit_window;
        
        ledit_window *ledit_window_create(ledit_common *common, ledit_theme *theme);
       t@@ -62,6 +63,7 @@ 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_mode_extra_text(ledit_window *window, char *text);
        
        void ledit_window_set_scroll_max(ledit_window *window, long max);
        void ledit_window_set_scroll_pos(ledit_window *window, long pos);