commit 7892e89a47ccb35549b25fe558245b9e8f639daf
parent 22ffb413e9e0be6a5764de4bcc951e6a0480bad3
Author: lumidify <nobody@lumidify.org>
Date: Sat, 18 Dec 2021 21:44:47 +0100
Start cleaning up and adding documentation
Diffstat:
M | IDEAS | | | 7 | +++++++ |
M | Makefile | | | 7 | +++++-- |
A | NOTES | | | 3 | +++ |
D | QUIRKS | | | 14 | -------------- |
M | README | | | 15 | ++++++++++++++- |
M | TODO | | | 3 | +++ |
M | assert.c | | | 17 | ----------------- |
M | buffer.c | | | 138 | +++++++++++++++++++++++++++++++++++++------------------------------------------ |
M | buffer.h | | | 30 | +++++++++++++++--------------- |
M | cache.c | | | 9 | +++------ |
M | cache.h | | | 9 | +++++++++ |
M | cleanup.h | | | 5 | +++++ |
M | common.h | | | 11 | +++++++++-- |
A | draw_util.c | | | 43 | +++++++++++++++++++++++++++++++++++++++++++ |
A | draw_util.h | | | 34 | ++++++++++++++++++++++++++++++++++ |
M | keys.c | | | 2 | +- |
M | keys.h | | | 32 | +++++++++----------------------- |
M | keys_basic.c | | | 163 | ++++++++++++++++++++++++++++++++++++++++++++++++------------------------------- |
M | keys_basic.h | | | 8 | ++++++++ |
M | keys_basic_config.h | | | 18 | +++++++++--------- |
M | keys_command.c | | | 84 | +++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------- |
M | keys_command.h | | | 8 | ++++++++ |
M | keys_command_config.h | | | 6 | +++--- |
A | keys_config.h | | | 31 | +++++++++++++++++++++++++++++++ |
A | ledit.1 | | | 796 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | ledit.c | | | 45 | +++++++++++++++++++-------------------------- |
M | macros.h | | | 5 | +++++ |
M | memory.c | | | 126 | +++++++++++++++++++++++++++++++++++++++++++++++-------------------------------- |
M | memory.h | | | 16 | ++++++++++++++++ |
M | pango-compat.h | | | 7 | +++++++ |
M | search.c | | | 49 | +++++++++++++++++++------------------------------ |
M | search.h | | | 22 | +++++++++++++++++----- |
M | theme.h | | | 8 | ++++++++ |
M | txtbuf.c | | | 22 | ++++++++-------------- |
M | txtbuf.h | | | 15 | ++++++++------- |
M | undo.c | | | 86 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------- |
M | undo.h | | | 25 | +++++++++++++++++++++---- |
M | util.c | | | 77 | ++++++++++++++++++++++++++++++----------------------------------------------- |
M | util.h | | | 34 | ++++++++++++++-------------------- |
M | view.c | | | 119 | ++++++++++++++++++++++++++++++++++--------------------------------------------- |
M | view.h | | | 26 | ++++++++++++++------------ |
M | window.c | | | 28 | +++++++++++++--------------- |
M | window.h | | | 28 | ++++++++++++++++++++++------ |
43 files changed, 1642 insertions(+), 589 deletions(-)
diff --git a/IDEAS b/IDEAS
@@ -3,3 +3,10 @@
* basic macros
* add different (more basic) text backend
* https://drewdevault.com/2021/06/27/You-cant-capture-the-nuance.html
+* maybe somehow allow to define one keyboard layout that can be easily
+ be switched to and from - this would make typing commands easier
+ because they can't really be translated into other languages like the
+ key bindings
+ -> I'm not sure it that's even possible in a portable way, though,
+ since the keyboard layouts can be set in many different ways, so
+ the entire state would somehow have to be saved to restore it again.
diff --git a/Makefile b/Makefile
@@ -24,6 +24,7 @@ OBJ = \
txtbuf.o \
undo.o \
util.o \
+ draw_util.o \
window.o \
pango-compat.o
@@ -42,6 +43,7 @@ HDR = \
txtbuf.h \
undo.h \
util.h \
+ draw_util.h \
window.h \
cleanup.h \
pango-compat.h
@@ -53,8 +55,9 @@ all: ${BIN}
ledit.o window.o : config.h
theme.o : theme_config.h
-keys_basic.o : keys_basic_config.h
-keys_command.o : keys_command_config.h
+keys_basic.o : keys_basic_config.h keys_config.h
+keys_command.o : keys_command_config.h keys_config.h
+keys.o : keys_config.h
${OBJ} : ${HDR}
diff --git a/NOTES b/NOTES
@@ -0,0 +1,3 @@
+* I originally wanted to avoid putting includes in header files, but
+ I eventually decided that it just becomes a huge mess that way.
+ Maybe I'll change my mind sometime, though.
diff --git a/QUIRKS b/QUIRKS
@@ -1,14 +0,0 @@
-* Undo with multiple views:
- Since a new mode group is started each time insert is entered, when text
- is typed in one view in insert, then in another view, and then again in
- the first one, the last two inserts will be undone in one go since both
- views were in insert already. I'm not sure how to make this more logical,
- though.
- Maybe it could be "improved" by also saving view in undo stack, but that
- would cause problems because views can be added and removed, and it would
- maybe not even be more logical.
-
-* Scroll offset is stored as pixel value, so a view may scroll when text is
- added or deleted in another view. Additionally, when a new view is created,
- the scroll offset from the old view is taken, which may be weird if the
- window of the new view is a different size.
diff --git a/README b/README
@@ -1 +1,14 @@
-Work in progress. Nothing to see here.
+WARNING: This is work in progress! A lot of bugs still need to be fixed
+before this can be used as a real text editor.
+
+ledit is a vi-like text editor for people who switch between keyboard
+layouts frequently and/or work with languages that require complex text
+layout.
+
+The documentation can be viewed in ledit.1 or at the following locations:
+
+gopher://lumidify.org/0/doc/ledit/ledit-current.txt
+gopher://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/0/doc/ledit/ledit-current.txt
+http://lumidify.org/doc/ledit/ledit-current.html
+http://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/doc/ledit/ledit-current.html
+https://lumidify.org/doc/ledit/ledit-current.html
diff --git a/TODO b/TODO
@@ -1,3 +1,6 @@
+* Use proper gap buffer implementation
+* Somehow clean up overflow handling
+* File locking (lockf(3) or flock(2))
* Load file in background so text is already shown while still
loading the rest of a big file.
* Try to copy vi behavior where the cursor jumps back to the original
diff --git a/assert.c b/assert.c
@@ -1,22 +1,5 @@
-/* FIXME: sort out the stupid includes */
#include <stdio.h>
#include <stdlib.h>
-
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-#include <pango/pangoxft.h>
-#include <X11/extensions/Xdbe.h>
-
-#include "pango-compat.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 "cleanup.h"
void
diff --git a/buffer.c b/buffer.c
@@ -1,5 +1,3 @@
-/* FIXME: shrink buffers when text length less than a fourth of the size */
-/* FIXME: handle all undo within buffer to keep it consistent */
/* FIXME: maybe use separate unicode grapheme library so all functions
that need grapheme boundaries can be included here instead of in the views */
@@ -16,16 +14,17 @@
#include <pango/pangoxft.h>
#include <X11/extensions/Xdbe.h>
-#include "pango-compat.h"
-#include "memory.h"
-#include "common.h"
-#include "txtbuf.h"
+#include "util.h"
#include "undo.h"
#include "cache.h"
#include "theme.h"
+#include "memory.h"
+#include "common.h"
+#include "txtbuf.h"
#include "window.h"
#include "buffer.h"
#include "assert.h"
+#include "pango-compat.h"
/*
* Important notes:
@@ -43,11 +42,6 @@
*/
/*
- * Move the gap of a line so it is at byte position 'index'
- */
-static void move_text_gap(ledit_line *line, size_t index);
-
-/*
* Move the gap of the line gap buffer to 'index'.
*/
static void move_line_gap(ledit_buffer *buffer, size_t index);
@@ -150,27 +144,18 @@ static void buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1,
*/
static void buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length);
+/*
+ * Copy text range into given buffer.
+ * - dst is null-terminated
+ * - dst must be large enough to contain the text and NUL (only use this together with buffer_textlen)
+ * - the range must be sorted already
+ */
+static void buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2);
+
static void marklist_destroy(ledit_buffer_marklist *marklist);
static ledit_buffer_marklist *marklist_create(void);
static void
-swap_sz(size_t *a, size_t *b) {
- size_t tmp = *a;
- *a = *b;
- *b = tmp;
-}
-
-static void
-sort_range(size_t *l1, size_t *b1, size_t *l2, size_t *b2) {
- if (*l1 == *l2 && *b1 > *b2) {
- swap_sz(b1, b2);
- } else if (*l1 > *l2) {
- swap_sz(l1, l2);
- swap_sz(b1, b2);
- }
-}
-
-static void
marklist_destroy(ledit_buffer_marklist *marklist) {
for (size_t i = 0; i < marklist->len; i++) {
free(marklist->marks[i].text);
@@ -190,7 +175,7 @@ buffer_insert_mark(ledit_buffer *buffer, char *mark, size_t len, size_t line, si
}
}
if (marklist->len == marklist->alloc) {
- size_t new_alloc = marklist->alloc > 0 ? marklist->alloc * 2 : 4;
+ size_t new_alloc = ideal_array_size(marklist->alloc, add_sz(marklist->len, 1));
marklist->marks = ledit_reallocarray(
marklist->marks, new_alloc, sizeof(ledit_buffer_mark)
);
@@ -299,10 +284,8 @@ buffer_set_hard_line_based(ledit_buffer *buffer, int hl) {
}
void
-buffer_add_view(ledit_buffer *buffer, ledit_theme *theme, enum ledit_mode mode, size_t line, size_t pos, long scroll_offset) {
- size_t new_num = buffer->views_num + 1;
- if (new_num <= buffer->views_num)
- err_overflow();
+buffer_add_view(ledit_buffer *buffer, ledit_theme *theme, ledit_mode mode, size_t line, size_t pos, long scroll_offset) {
+ size_t new_num = add_sz(buffer->views_num, 1);
buffer->views = ledit_reallocarray(buffer->views, new_num, sizeof(ledit_view *));
buffer->views[buffer->views_num] = view_create(buffer, theme, mode, line, pos);
set_view_hard_line_text(buffer, buffer->views[buffer->views_num]);
@@ -354,10 +337,11 @@ buffer_load_file(ledit_buffer *buffer, char *filename, size_t line, char **errst
len = ftell(file);
if (len < 0) goto errorclose;
if (fseek(file, 0, SEEK_SET)) goto errorclose;
+ size_t lenz = add_sz((size_t)len, 2);
ll = buffer_get_line(buffer, line);
/* FIXME: insert in chunks instead of allocating huge buffer */
- file_contents = ledit_malloc(len + 2);
+ file_contents = ledit_malloc(lenz);
/* mimic nvi (or at least the openbsd version) - if the line
is empty, insert directly, otherwise insert after the line */
if (ll->len > 0) {
@@ -485,8 +469,9 @@ buffer_insert_text_from_line_base(
txtbuf *text_ret) {
ledit_assert(dst_line != src_line);
ledit_line *ll = buffer_get_line(buffer, src_line);
+ ledit_assert(add_sz(src_index, src_len) <= ll->len);
if (text_ret != NULL) {
- txtbuf_grow(text_ret, src_len);
+ txtbuf_resize(text_ret, src_len);
text_ret->len = src_len;
}
if (src_index >= ll->gap) {
@@ -547,16 +532,6 @@ buffer_insert_text_from_line_base(
}
static void
-move_text_gap(ledit_line *line, size_t index) {
- /* yes, I know sizeof(char) == 1 anyways */
- move_gap(
- line->text, sizeof(char), index,
- line->gap, line->cap, line->len,
- &line->gap
- );
-}
-
-static void
move_line_gap(ledit_buffer *buffer, size_t index) {
move_gap(
buffer->lines, sizeof(ledit_line), index,
@@ -593,12 +568,9 @@ resize_and_move_line_gap(ledit_buffer *buffer, size_t min_size, size_t index) {
static void
buffer_insert_text_base(ledit_buffer *buffer, size_t line_index, size_t index, char *text, size_t len) {
ledit_line *line = buffer_get_line(buffer, line_index);
+ ledit_assert(index <= line->len);
/* \0 is not included in line->len */
- /* FIXME: this if should be redundant now because resize_and_move... includes a check */
- if (line->len + len + 1 > line->cap || line->text == NULL)
- resize_and_move_text_gap(line, line->len + len + 1, index);
- else
- move_text_gap(line, index);
+ resize_and_move_text_gap(line, add_sz3(line->len, len, 1), index);
/* the gap is now located at 'index' and least large enough to hold the new text */
memcpy(line->text + index, text, len);
line->gap += len;
@@ -660,7 +632,7 @@ buffer_insert_text_with_newlines_base(
rem_len -= cur - last + 1;
last = cur + 1;
}
- /* FIXME: check how legal this casting between pointers and ints is */
+ /* FIXME: check how legal this casting between pointers and size_t's is */
buffer_insert_text_base(buffer, cur_line, cur_index, last, text + len - last);
if (end_line_ret)
*end_line_ret = cur_line;
@@ -678,15 +650,10 @@ init_line(ledit_buffer *buffer, ledit_line *line) {
line->len = 0;
}
-/* FIXME: error checking (index out of bounds, etc.) */
static void
buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text) {
- size_t new_len = buffer->lines_num + 1;
- if (new_len <= buffer->lines_num)
- err_overflow();
- size_t insert_index = line_index + 1;
- if (insert_index <= line_index)
- err_overflow();
+ size_t new_len = add_sz(buffer->lines_num, 1);
+ size_t insert_index = add_sz(line_index, 1);
resize_and_move_line_gap(buffer, new_len, insert_index);
buffer->lines_num++;
buffer->lines_gap++;
@@ -721,6 +688,11 @@ buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t inde
}
move_line_gap(buffer, index1);
buffer->lines_num -= index2 - index1 + 1;
+ /* possibly decrease size of array - this needs to be after
+ actually deleting the lines so the length is already less */
+ size_t min_size = ideal_array_size(buffer->lines_cap, buffer->lines_num);
+ if (min_size != buffer->lines_cap)
+ resize_and_move_line_gap(buffer, buffer->lines_num, buffer->lines_gap);
for (size_t i = 0; i < buffer->views_num; i++) {
view_notify_delete_lines(buffer->views[i], index1, index2);
}
@@ -760,14 +732,24 @@ buffer_textlen(ledit_buffer *buffer, size_t line1, size_t byte1, size_t line2, s
ledit_assert(line1 < line2 || (line1 == line2 && byte1 <= byte2));
size_t len = 0;
ledit_line *ll = buffer_get_line(buffer, line1);
+ ledit_line *ll2 = buffer_get_line(buffer, line2);
+ ledit_assert(byte1 <= ll->len);
+ ledit_assert(byte2 <= ll2->len);
if (line1 == line2) {
len = byte2 - byte1;
} else {
/* + 1 for newline */
- len = ll->len - byte1 + byte2 + 1;
+ len = add_sz3(ll->len - byte1, byte2, 1);
for (size_t i = line1 + 1; i < line2; i++) {
ll = buffer_get_line(buffer, i);
- len += ll->len + 1;
+ /* ll->len + 1 should be valid anyways
+ because there *should* always be
+ space for '\0' at the end, i.e. ll->cap
+ should be at least ll->len + 1 */
+ /* FIXME: also, this overflow checking is
+ probably completely useless (it definitely
+ is really ugly) */
+ len += add_sz(ll->len, 1);
}
}
return len;
@@ -779,7 +761,7 @@ buffer_textlen(ledit_buffer *buffer, size_t line1, size_t byte1, size_t line2, s
only done when it is re-rendered (and thus normalized because
of pango's requirements). If a more efficient rendering
backend is added, it would be good to optimize this, though. */
-void
+static void
buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2) {
ledit_assert(line1 < line2 || (line1 == line2 && byte1 <= byte2));
ledit_line *ll1 = buffer_get_line(buffer, line1);
@@ -817,7 +799,7 @@ buffer_copy_text_to_txtbuf(
size_t line2, size_t byte2) {
ledit_assert(line1 < line2 || (line1 == line2 && byte1 <= byte2));
size_t len = buffer_textlen(buffer, line1, byte1, line2, byte2);
- txtbuf_grow(buf, len + 1);
+ txtbuf_resize(buf, len);
buffer_copy_text(buffer, buf->text, line1, byte1, line2, byte2);
buf->len = len;
}
@@ -831,7 +813,6 @@ line_prev_utf8(ledit_line *line, size_t index) {
return 0;
size_t 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;
@@ -865,6 +846,8 @@ line_byte_to_char(ledit_line *line, size_t byte) {
static void
buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length) {
ledit_line *l = buffer_get_line(buffer, line);
+ (void)add_sz(start, length); /* just check that no overflow */
+ ledit_assert(start + length <= l->len);
if (start <= l->gap && start + length >= l->gap) {
l->gap = start;
} else if (start < l->gap && start + length < l->gap) {
@@ -882,6 +865,10 @@ buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start,
);
}
l->len -= length;
+ /* possibly decrease size of line */
+ size_t cap = ideal_array_size(l->cap, add_sz(l->len, 1));
+ if (cap != l->cap)
+ resize_and_move_text_gap(l, l->len + 1, l->gap);
for (size_t i = 0; i < buffer->views_num; i++) {
view_notify_delete_text(buffer->views[i], line, start, length);
}
@@ -915,6 +902,8 @@ delete_range_base(
}
ledit_line *line1 = buffer_get_line(buffer, line_index1);
ledit_line *line2 = buffer_get_line(buffer, line_index2);
+ ledit_assert(byte_index1 <= line1->len);
+ ledit_assert(byte_index2 <= line2->len);
buffer_delete_line_section_base(
buffer, line_index1, byte_index1, line1->len - byte_index1
);
@@ -942,26 +931,26 @@ undo_delete_helper(void *data, size_t line1, size_t byte1, size_t line2, size_t
delete_range_base(buffer, line1, byte1, line2, byte2, NULL);
}
-void
-buffer_undo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size_t *cur_byte) {
+undo_status
+buffer_undo(ledit_buffer *buffer, ledit_mode mode, size_t *cur_line, size_t *cur_byte) {
size_t min_line;
- ledit_undo(
+ undo_status s = ledit_undo(
buffer->undo, mode, buffer, &undo_insert_helper,
&undo_delete_helper, cur_line, cur_byte, &min_line
);
- /* FIXME: why is this check here? */
if (min_line < buffer->lines_num) {
buffer_recalc_all_views_from_line(
buffer, min_line > 0 ? min_line - 1 : min_line
);
}
buffer->modified = 1;
+ return s;
}
-void
-buffer_redo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size_t *cur_byte) {
+undo_status
+buffer_redo(ledit_buffer *buffer, ledit_mode mode, size_t *cur_line, size_t *cur_byte) {
size_t min_line;
- ledit_redo(
+ undo_status s = ledit_redo(
buffer->undo, mode, buffer, &undo_insert_helper,
&undo_delete_helper, cur_line, cur_byte, &min_line
);
@@ -971,12 +960,13 @@ buffer_redo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size_t
);
}
buffer->modified = 1;
+ return s;
}
void
buffer_delete_with_undo_base(
ledit_buffer *buffer, ledit_range cur_range,
- int start_undo_group, enum ledit_mode mode, /* for undo */
+ int start_undo_group, ledit_mode mode, /* for undo */
size_t line_index1, size_t byte_index1,
size_t line_index2, size_t byte_index2,
txtbuf *text_ret) {
@@ -1001,7 +991,7 @@ buffer_delete_with_undo_base(
void
buffer_delete_with_undo(
ledit_buffer *buffer, ledit_range cur_range,
- int start_undo_group, enum ledit_mode mode, /* for undo */
+ int start_undo_group, ledit_mode mode, /* for undo */
size_t line_index1, size_t byte_index1,
size_t line_index2, size_t byte_index2,
txtbuf *text_ret) {
@@ -1020,7 +1010,7 @@ void
buffer_insert_with_undo_base(
ledit_buffer *buffer,
ledit_range cur_range, int set_range_end,
- int start_undo_group, enum ledit_mode mode,
+ int start_undo_group, ledit_mode mode,
size_t line, size_t byte,
char *text, size_t len,
size_t *line_ret, size_t *byte_ret) {
@@ -1053,7 +1043,7 @@ void
buffer_insert_with_undo(
ledit_buffer *buffer,
ledit_range cur_range, int set_range_end,
- int start_undo_group, enum ledit_mode mode,
+ int start_undo_group, ledit_mode mode,
size_t line, size_t byte,
char *text, size_t len,
size_t *line_ret, size_t *byte_ret) {
diff --git a/buffer.h b/buffer.h
@@ -1,6 +1,14 @@
#ifndef _LEDIT_BUFFER_H_
#define _LEDIT_BUFFER_H_
+#include <time.h>
+#include <stdio.h>
+#include <stddef.h>
+
+#include "common.h"
+#include "txtbuf.h"
+#include "undo.h"
+
typedef struct ledit_buffer ledit_buffer;
#include "view.h"
@@ -72,7 +80,7 @@ void buffer_set_hard_line_based(ledit_buffer *buffer, int hl);
*/
void buffer_add_view(
ledit_buffer *buffer, ledit_theme *theme,
- enum ledit_mode mode, size_t line, size_t pos, long scroll_offset
+ ledit_mode mode, size_t line, size_t pos, long scroll_offset
);
/*
@@ -165,14 +173,6 @@ void buffer_recalc_all_lines(ledit_buffer *buffer);
size_t buffer_textlen(ledit_buffer *buffer, size_t line1, size_t byte1, size_t line2, size_t byte2);
/*
- * Copy text range into given buffer.
- * - dst is null-terminated
- * - dst must be large enough to contain the text and NUL (only use this together with buffer_textlen)
- * - the range must be sorted already
- */
-void buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2);
-
-/*
* Copy text range into given buffer and resize it if necessary.
* - the range must be sorted already
*/
@@ -219,12 +219,12 @@ int buffer_get_mark(ledit_buffer *buffer, char *mark, size_t len, size_t *line_r
* 'cur_line' and 'cur_byte' are filled with the new line and cursor
* position after the undo.
*/
-void buffer_undo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size_t *cur_byte);
+undo_status buffer_undo(ledit_buffer *buffer, ledit_mode mode, size_t *cur_line, size_t *cur_byte);
/*
* Same as 'buffer_undo', but for redo.
*/
-void buffer_redo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size_t *cur_byte);
+undo_status buffer_redo(ledit_buffer *buffer, ledit_mode mode, size_t *cur_line, size_t *cur_byte);
/*
* Delete the given range (which does not need to be sorted yet) and
@@ -237,7 +237,7 @@ void buffer_redo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, s
*/
void buffer_delete_with_undo_base(
ledit_buffer *buffer, ledit_range cur_range,
- int start_undo_group, enum ledit_mode mode, /* for undo */
+ int start_undo_group, ledit_mode mode, /* for undo */
size_t line_index1, size_t byte_index1,
size_t line_index2, size_t byte_index2,
txtbuf *text_ret
@@ -249,7 +249,7 @@ void buffer_delete_with_undo_base(
*/
void buffer_delete_with_undo(
ledit_buffer *buffer, ledit_range cur_range,
- int start_undo_group, enum ledit_mode mode, /* for undo */
+ int start_undo_group, ledit_mode mode, /* for undo */
size_t line_index1, size_t byte_index1,
size_t line_index2, size_t byte_index2,
txtbuf *text_ret
@@ -268,7 +268,7 @@ void buffer_delete_with_undo(
void buffer_insert_with_undo_base(
ledit_buffer *buffer,
ledit_range cur_range, int set_range_end,
- int start_undo_group, enum ledit_mode mode,
+ int start_undo_group, ledit_mode mode,
size_t line, size_t byte,
char *text, size_t len,
size_t *line_ret, size_t *byte_ret
@@ -281,7 +281,7 @@ void buffer_insert_with_undo_base(
void buffer_insert_with_undo(
ledit_buffer *buffer,
ledit_range cur_range, int set_range_end,
- int start_undo_group, enum ledit_mode mode,
+ int start_undo_group, ledit_mode mode,
size_t line, size_t byte,
char *text, size_t len,
size_t *line_ret, size_t *byte_ret
diff --git a/cache.c b/cache.c
@@ -6,6 +6,7 @@
#include <pango/pangoxft.h>
#include <X11/extensions/Xdbe.h>
+#include "util.h"
#include "common.h"
#include "memory.h"
#include "cache.h"
@@ -94,9 +95,6 @@ cache_get_layout(ledit_cache *cache, size_t index) {
return &cache->layouts[index];
}
-/* FIXME: decide on int or size_t, but not both */
-/* or maybe ssize_t */
-
/* FIXME: max pixmap cache size */
void
cache_assign_pixmap_index(
@@ -126,10 +124,9 @@ cache_assign_pixmap_index(
}
/* no free entry found, increase cache size */
- /* FIXME: what is the ideal size to resize to? */
- /* FIXME: overflow */
/* FIXME: maybe have maximum cache size */
- cache->pixmaps = ledit_reallocarray(cache->pixmaps, cache->num_pixmaps * 2, sizeof(cache_pixmap));
+ size_t new_alloc = ideal_array_size(cache->num_pixmaps, add_sz(cache->num_pixmaps, 1));
+ cache->pixmaps = ledit_reallocarray(cache->pixmaps, new_alloc, sizeof(cache_pixmap));
entry_index = cache->num_pixmaps;
for (size_t i = cache->num_pixmaps; i < cache->num_pixmaps * 2; i++) {
cache->pixmaps[i].line = 0;
diff --git a/cache.h b/cache.h
@@ -1,3 +1,10 @@
+#ifndef _CACHE_H_
+#define _CACHE_H_
+
+#include <stddef.h>
+#include <X11/Xlib.h>
+#include <pango/pangoxft.h>
+
/*
*The maximum number of layouts in the cache.
*/
@@ -129,3 +136,5 @@ void cache_assign_layout_index(
void (*set_layout_line)(void *, size_t, size_t),
void (*invalidate_layout_line)(void *, size_t)
);
+
+#endif
diff --git a/cleanup.h b/cleanup.h
@@ -1,4 +1,9 @@
+#ifndef _CLEANUP_H_
+#define _CLEANUP_H_
+
/* This is here so it can be called from other places
even though the function definition is in ledit.c */
void ledit_cleanup(void);
void ledit_emergencydump(void);
+
+#endif
diff --git a/common.h b/common.h
@@ -1,3 +1,8 @@
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include <X11/Xlib.h>
+
typedef struct {
size_t line1;
size_t byte1;
@@ -5,11 +10,11 @@ typedef struct {
size_t byte2;
} ledit_range;
-enum ledit_mode {
+typedef enum {
NORMAL = 1,
INSERT = 2,
VISUAL = 4
-};
+} ledit_mode;
typedef struct {
Display *dpy;
@@ -19,3 +24,5 @@ typedef struct {
int depth;
int redraw;
} ledit_common;
+
+#endif
diff --git a/draw_util.c b/draw_util.c
@@ -0,0 +1,43 @@
+#include <X11/Xlib.h>
+#include <X11/Xft/Xft.h>
+
+#include "memory.h"
+#include "window.h"
+#include "draw_util.h"
+
+ledit_draw *
+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(
+ window->common->dpy, window->drawable, w, h, window->common->depth
+ );
+ draw->xftdraw = XftDrawCreate(
+ window->common->dpy, draw->pixmap, window->common->vis, window->common->cm
+ );
+ return draw;
+}
+
+void
+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(window->common->dpy, draw->pixmap);
+ draw->pixmap = XCreatePixmap(
+ window->common->dpy, window->drawable,
+ draw->w, draw->h, window->common->depth
+ );
+ XftDrawChange(draw->xftdraw, draw->pixmap);
+ }
+}
+
+void
+draw_destroy(ledit_window *window, ledit_draw *draw) {
+ XFreePixmap(window->common->dpy, draw->pixmap);
+ XftDrawDestroy(draw->xftdraw);
+ free(draw);
+}
diff --git a/draw_util.h b/draw_util.h
@@ -0,0 +1,34 @@
+#ifndef _DRAW_UTIL_H_
+#define _DRAW_UTIL_H_
+
+#include <X11/Xlib.h>
+#include <X11/Xft/Xft.h>
+
+#include "window.h"
+
+/*
+ * This is just a basic wrapper for XftDraws and Pixmaps
+ * that is used by the window for its text display at the bottom.
+ */
+typedef struct {
+ XftDraw *xftdraw;
+ Pixmap pixmap;
+ int w, h;
+} ledit_draw;
+
+/*
+ * Create a draw with the specified width and height.
+ */
+ledit_draw *draw_create(ledit_window *window, int w, int h);
+
+/*
+ * Make sure the size of the draw is at least the given width and height.
+ */
+void draw_grow(ledit_window *window, ledit_draw *draw, int w, int h);
+
+/*
+ * Destroy a draw.
+ */
+void draw_destroy(ledit_window *window, ledit_draw *draw);
+
+#endif
diff --git a/keys.c b/keys.c
@@ -13,6 +13,7 @@
#include "theme.h"
#include "window.h"
#include "keys.h"
+#include "keys_config.h"
KEY_LANGS;
@@ -32,7 +33,6 @@ get_language_index(char *lang) {
/* FIXME: The Mod*Masks can be remapped, so it isn't really clear what is what */
/* most are disabled now to avoid issues with e.g. numlock */
static unsigned int importantmod = ShiftMask | ControlMask | Mod1Mask;
-#define XK_ANY_MOD UINT_MAX
int
match_key(unsigned int mask, unsigned int state)
diff --git a/keys.h b/keys.h
@@ -1,29 +1,13 @@
-#define LENGTH(X) (sizeof(X) / sizeof(X[0]))
+#ifndef _KEYS_H_
+#define _KEYS_H_
-/*
- * These are the language strings compared with the language strings that
- * xkb gives in order to change the key mapping on layout change events.
- */
-#define KEY_LANGS \
-static char *key_langs[] = { \
- "English (US)", \
- "German", \
- "Urdu (Pakistan)", \
- "Hindi (Bolnagri)" \
-}
+#include <X11/Xlib.h>
+#include "window.h"
-#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 LENGTH(X) (sizeof(X) / sizeof(X[0]))
-#define LANG_KEYS(index) &keys[index]
+#define XK_ANY_MOD UINT_MAX
+#define XK_NO_MOD 0
/* get the index of a language with the given name, or -1 if none exists */
int get_language_index(char *lang);
@@ -34,3 +18,5 @@ void preprocess_key(
ledit_window *window, XEvent *event, KeySym *sym_ret,
char *buf_ret, int buf_size, int *buf_len_ret
);
+
+#endif
diff --git a/keys_basic.c b/keys_basic.c
@@ -7,8 +7,8 @@
-> space is hidden when e.g. ltr text left and rtl text on right is wrapped */
/* FIXME: some weird things still happen with selections staying as "ghosts"
and being deleted at some later time even though they're not shown anymore */
-/* FIXME: there seem to be some issues with undo, but I couldn't reproduce
- them reliably yet */
+/* FIXME: delete everything concerned with selections in insert mode since
+ they are now not allowed at all */
#include <stdio.h>
#include <stdlib.h>
@@ -20,6 +20,7 @@
#include <X11/XF86keysym.h>
#include <X11/cursorfont.h>
+#include "util.h"
#include "memory.h"
#include "common.h"
#include "txtbuf.h"
@@ -32,6 +33,7 @@
#include "search.h"
#include "keys.h"
+#include "keys_config.h"
#include "keys_basic.h"
#include "keys_command.h"
#include "keys_basic_config.h"
@@ -108,7 +110,7 @@ void clear_key_stack(void);
static void move_cursor_left_right(ledit_view *view, int dir, int allow_illegal_index);
static void move_cursor_up_down(ledit_view *view, int dir);
-static void push_num(int num);
+static void push_num(ledit_view *view, int num);
static void delete_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type);
static void yank_cb(ledit_view *view, size_t line, size_t char_pos, enum key_type type);
static void get_new_line_softline(
@@ -129,14 +131,6 @@ view_locked_error(ledit_view *view) {
#define CHECK_VIEW_LOCKED(view) if (view->lock_text) return view_locked_error(view)
#define CHECK_VIEW_LOCKED_NORETURN(view) if (view->lock_text) (void)view_locked_error(view)
-/* FIXME: move to common */
-static void
-swap_sz(size_t *a, size_t *b) {
- size_t tmp = *a;
- *a = *b;
- *b = tmp;
-}
-
static int
key_stack_empty(void) {
return key_stack.len == 0;
@@ -146,7 +140,7 @@ 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;
+ size_t new_alloc = ideal_array_size(key_stack.alloc, add_sz(key_stack.len, 1));
key_stack.stack = ledit_reallocarray(
key_stack.stack, new_alloc, sizeof(struct key_stack_elem)
);
@@ -207,7 +201,7 @@ err_invalid_key(ledit_view *view) {
* possibly a second one if the top one was a number key.
*/
static int
-get_key_repeat_and_motion_cb(motion_callback *cb_ret) {
+get_key_repeat_and_motion_cb(ledit_view *view, motion_callback *cb_ret) {
int num = 1;
struct key_stack_elem *e = pop_key_stack();
if (e != NULL) {
@@ -220,7 +214,10 @@ get_key_repeat_and_motion_cb(motion_callback *cb_ret) {
if (e != NULL) {
int new_count = e->count > 0 ? e->count : 1;
if (INT_MAX / new_count < num) {
- /* FIXME: show error */
+ window_show_message(
+ view->window,
+ "Integer overflow in key repetition", -1
+ );
num = INT_MAX;
}
num *= new_count;
@@ -274,7 +271,7 @@ static struct repetition_stack_elem *
push_repetition_stack(void) {
struct repetition_stack_elem *e;
if (repetition_stack.tmp_len >= repetition_stack.tmp_alloc) {
- size_t new_alloc = repetition_stack.tmp_alloc > 0 ? repetition_stack.tmp_alloc * 2 : 4;
+ size_t new_alloc = ideal_array_size(repetition_stack.tmp_alloc, add_sz(repetition_stack.tmp_len, 1));
repetition_stack.tmp_stack = ledit_reallocarray(
repetition_stack.tmp_stack,
new_alloc, sizeof(struct repetition_stack_elem)
@@ -507,6 +504,7 @@ delete_chars_forwards(ledit_view *view, char *text, size_t len) {
view, view->cur_line, view->cur_index
);
view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
+ finalize_repetition_stack();
return (struct action){ACTION_NONE, NULL};
}
@@ -537,6 +535,7 @@ delete_chars_backwards(ledit_view *view, char *text, size_t len) {
view, view->cur_line, start_index
);
view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
+ finalize_repetition_stack();
return (struct action){ACTION_NONE, NULL};
}
@@ -615,7 +614,7 @@ move_to_line(ledit_view *view, char *text, size_t len) {
(void)text;
(void)len;
motion_callback cb = NULL;
- int repeat = get_key_repeat_and_motion_cb(&cb);
+ int repeat = get_key_repeat_and_motion_cb(view, &cb);
size_t line;
if (repeat > 0)
line = (size_t)repeat > view->lines_num ? view->lines_num : (size_t)repeat;
@@ -624,7 +623,11 @@ move_to_line(ledit_view *view, char *text, size_t len) {
else
return err_invalid_key(view);
if (cb != NULL) {
- cb(view, line - 1, 0, KEY_MOTION_LINE);
+ /* this is a bit of a hack - because move_to_line always works
+ with hard lines, it sets the index to ll->len so e.g. the delete
+ callback deletes until the end of the line even in soft line mode */
+ ledit_line *ll = buffer_get_line(view->buffer, line - 1);
+ cb(view, line - 1, ll->len, KEY_MOTION_LINE);
} else {
view_wipe_line_cursor_attrs(view, view->cur_line);
view->cur_line = line - 1;
@@ -822,6 +825,7 @@ screen_up(ledit_view *view, char *text, size_t len) {
(void)text;
(void)len;
int repeat = get_key_repeat();
+ /* FIXME: overflow */
if (repeat >= 0)
move_half_screen(view, -(repeat == 0 ? 2 : repeat*2));
else
@@ -903,7 +907,7 @@ change(ledit_view *view, char *text, size_t len) {
(void)len;
CHECK_VIEW_LOCKED(view);
motion_callback cb = NULL;
- int num = get_key_repeat_and_motion_cb(&cb);
+ int num = get_key_repeat_and_motion_cb(view, &cb);
if (num == -1)
return err_invalid_key(view);
if (view->mode == VISUAL) {
@@ -971,7 +975,7 @@ yank(ledit_view *view, char *text, size_t len) {
if (!paste_buffer)
paste_buffer = txtbuf_new();
if (view->mode == VISUAL) {
- view_sort_selection(
+ sort_range(
&view->sel.line1, &view->sel.byte1, &view->sel.line2, &view->sel.byte2
);
buffer_copy_text_to_txtbuf(
@@ -990,7 +994,7 @@ yank(ledit_view *view, char *text, size_t len) {
clear_key_stack();
} else {
motion_callback cb = NULL;
- int num = get_key_repeat_and_motion_cb(&cb);
+ int num = get_key_repeat_and_motion_cb(view, &cb);
if (num == 0)
num = 1;
if (cb == &yank_cb) {
@@ -1086,7 +1090,7 @@ delete(ledit_view *view, char *text, size_t len) {
(void)len;
CHECK_VIEW_LOCKED(view);
motion_callback cb = NULL;
- int num = get_key_repeat_and_motion_cb(&cb);
+ int num = get_key_repeat_and_motion_cb(view, &cb);
if (num == -1)
return err_invalid_key(view);
if (delete_selection(view)) {
@@ -1255,20 +1259,27 @@ paste_normal_backwards(ledit_view *view, char *text, size_t len) {
}
static void
-push_num(int num) {
+push_num(ledit_view *view, 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 messages */
if (INT_MAX / 10 < e->count) {
+ window_show_message(
+ view->window,
+ "Integer overflow in key repetition", -1
+ );
clear_key_stack();
return;
}
e->count *= 10;
if (INT_MAX - num < e->count) {
+ window_show_message(
+ view->window,
+ "Integer overflow in key repetition", -1
+ );
clear_key_stack();
return;
}
@@ -1280,7 +1291,7 @@ push_0(ledit_view *view, char *text, size_t len) {
(void)view;
(void)text;
(void)len;
- push_num(0);
+ push_num(view, 0);
return (struct action){ACTION_NONE, NULL};
}
@@ -1289,7 +1300,7 @@ push_1(ledit_view *view, char *text, size_t len) {
(void)view;
(void)text;
(void)len;
- push_num(1);
+ push_num(view, 1);
return (struct action){ACTION_NONE, NULL};
}
@@ -1298,7 +1309,7 @@ push_2(ledit_view *view, char *text, size_t len) {
(void)view;
(void)text;
(void)len;
- push_num(2);
+ push_num(view, 2);
return (struct action){ACTION_NONE, NULL};
}
@@ -1307,7 +1318,7 @@ push_3(ledit_view *view, char *text, size_t len) {
(void)view;
(void)text;
(void)len;
- push_num(3);
+ push_num(view, 3);
return (struct action){ACTION_NONE, NULL};
}
@@ -1316,7 +1327,7 @@ push_4(ledit_view *view, char *text, size_t len) {
(void)view;
(void)text;
(void)len;
- push_num(4);
+ push_num(view, 4);
return (struct action){ACTION_NONE, NULL};
}
@@ -1325,7 +1336,7 @@ push_5(ledit_view *view, char *text, size_t len) {
(void)view;
(void)text;
(void)len;
- push_num(5);
+ push_num(view, 5);
return (struct action){ACTION_NONE, NULL};
}
@@ -1334,7 +1345,7 @@ push_6(ledit_view *view, char *text, size_t len) {
(void)view;
(void)text;
(void)len;
- push_num(6);
+ push_num(view, 6);
return (struct action){ACTION_NONE, NULL};
}
@@ -1343,7 +1354,7 @@ push_7(ledit_view *view, char *text, size_t len) {
(void)view;
(void)text;
(void)len;
- push_num(7);
+ push_num(view, 7);
return (struct action){ACTION_NONE, NULL};
}
@@ -1352,7 +1363,7 @@ push_8(ledit_view *view, char *text, size_t len) {
(void)view;
(void)text;
(void)len;
- push_num(8);
+ push_num(view, 8);
return (struct action){ACTION_NONE, NULL};
}
@@ -1361,7 +1372,7 @@ push_9(ledit_view *view, char *text, size_t len) {
(void)view;
(void)text;
(void)len;
- push_num(9);
+ push_num(view, 9);
return (struct action){ACTION_NONE, NULL};
}
@@ -1413,7 +1424,7 @@ move_to_eol(ledit_view *view, char *text, size_t len) {
(void)len;
CHECK_VIEW_LOCKED(view);
motion_callback cb;
- int num = get_key_repeat_and_motion_cb(&cb);
+ int num = get_key_repeat_and_motion_cb(view, &cb);
if (num == -1)
return err_invalid_key(view);
if (num == 0)
@@ -1461,7 +1472,7 @@ name(ledit_view *view, char *text, size_t len) {
(void)text; \
(void)len; \
motion_callback cb; \
- int num = get_key_repeat_and_motion_cb(&cb); \
+ int num = get_key_repeat_and_motion_cb(view, &cb); \
if (num == -1) \
return err_invalid_key(view); \
if (num == 0) \
@@ -1510,7 +1521,7 @@ GEN_WORD_MOVEMENT(prev_bigword, view_prev_bigword)
static void
move_cursor_left_right(ledit_view *view, int dir, int allow_illegal_index) {
motion_callback cb;
- int num = get_key_repeat_and_motion_cb(&cb);
+ int num = get_key_repeat_and_motion_cb(view, &cb);
if (num == -1)
(void)err_invalid_key(view);
if (num == 0)
@@ -1540,6 +1551,8 @@ move_cursor_left_right(ledit_view *view, int dir, int allow_illegal_index) {
if (view->mode == VISUAL) {
view_set_selection(view, view->sel.line1, view->sel.byte1, view->cur_line, new_index);
} else if (view->mode == INSERT && view->sel_valid) {
+ /* FIXME: I guess this is unnecessary now that no
+ selection is allowed in insert mode */
view_wipe_selection(view);
} else if (view->mode == NORMAL) {
view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
@@ -1618,8 +1631,11 @@ enter_insert(ledit_view *view, char *text, size_t len) {
(void)text;
(void)len;
CHECK_VIEW_LOCKED(view);
- if (view->mode == NORMAL)
+ if (view->mode == NORMAL) {
view_wipe_line_cursor_attrs(view, view->cur_line);
+ } else if (view->mode == VISUAL) {
+ view_wipe_selection(view);
+ }
view_set_mode(view, INSERT);
clear_key_stack();
return (struct action){ACTION_NONE, NULL};
@@ -1632,7 +1648,7 @@ move_cursor_up_down(ledit_view *view, int dir) {
int new_softline;
motion_callback cb;
- int num = get_key_repeat_and_motion_cb(&cb);
+ int num = get_key_repeat_and_motion_cb(view, &cb);
if (num == -1)
(void)err_invalid_key(view);
if (num == 0)
@@ -1649,8 +1665,6 @@ move_cursor_up_down(ledit_view *view, int dir) {
view_get_softline_bounds(view, new_line, new_softline, &start, &end);
cb(view, new_line, start, KEY_MOTION_LINE);
} else {
- /* FIXME: when selecting on last line, moving down moves the cursor back
- one (when it stays on the same line because it's the last one) */
int lineno, x;
view_pos_to_x_softline(view, view->cur_line, view->cur_index, &x, &lineno);
view->cur_index = view_x_softline_to_pos(view, new_line, x, new_softline);
@@ -1744,7 +1758,7 @@ cursor_to_first_non_ws(ledit_view *view, char *text, size_t len) {
(void)text;
(void)len;
motion_callback cb;
- int num = get_key_repeat_and_motion_cb(&cb);
+ int num = get_key_repeat_and_motion_cb(view, &cb);
if (num != 0)
return err_invalid_key(view);
size_t new_index = 0;
@@ -1778,7 +1792,7 @@ cursor_to_beginning(ledit_view *view, char *text, size_t len) {
(void)text;
(void)len;
motion_callback cb;
- int num = get_key_repeat_and_motion_cb(&cb);
+ int num = get_key_repeat_and_motion_cb(view, &cb);
if (num != 0)
return err_invalid_key(view);
/* FIXME: should anything be done with num? */
@@ -1831,13 +1845,11 @@ switch_selection_end(ledit_view *view, char *text, size_t len) {
return (struct action){ACTION_NONE, NULL};
}
-#define XK_ANY_MOD UINT_MAX
-#define XK_NO_MOD 0
-
static struct action
enter_commandedit(ledit_view *view, char *text, size_t len) {
(void)text;
(void)len;
+ /* FIXME: wipe selection? */
char *str = view->sel_valid ? ":'<,'>" : ":";
window_set_bottom_bar_text(view->window, str, -1);
window_set_bottom_bar_cursor(view->window, strlen(str));
@@ -1888,7 +1900,7 @@ static struct action
jump_to_mark_cb(ledit_view *view, char *text, size_t len) {
grab_char_cb = NULL;
motion_callback cb;
- int num = get_key_repeat_and_motion_cb(&cb);
+ int num = get_key_repeat_and_motion_cb(view, &cb);
if (num > 0)
return err_invalid_key(view);
size_t line = 0, index = 0;
@@ -1962,23 +1974,30 @@ static struct action
show_line(ledit_view *view, char *text, size_t len) {
(void)text;
(void)len;
- int textlen = snprintf(NULL, 0, "Line %zu of %zu", view->cur_line + 1, view->lines_num);
- char *str = ledit_malloc(textlen + 1);
- snprintf(str, textlen + 1, "Line %zu of %zu", view->cur_line + 1, view->lines_num);
- window_show_message(view->window, str, textlen);
+ window_show_message_fmt(
+ view->window,
+ "%s: %s: line %zu of %zu",
+ view->buffer->filename ? view->buffer->filename : "(no filename)",
+ view->buffer->modified ? "modified" : "unmodified",
+ add_sz(view->cur_line, 1), view->lines_num
+ );
discard_repetition_stack();
return (struct action){ACTION_NONE, NULL};
}
-/* FIXME: return status! */
static struct action
undo(ledit_view *view, char *text, size_t len) {
(void)text;
(void)len;
CHECK_VIEW_LOCKED(view);
+ int num = get_key_repeat();
+ if (num == -1)
+ return err_invalid_key(view);
+ if (num == 0)
+ num = 1;
view_wipe_selection(view);
view_wipe_line_cursor_attrs(view, view->cur_line);
- view_undo(view);
+ view_undo(view, num);
view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
finalize_repetition_stack();
return (struct action){ACTION_NONE, NULL};
@@ -1989,9 +2008,14 @@ redo(ledit_view *view, char *text, size_t len) {
(void)text;
(void)len;
CHECK_VIEW_LOCKED(view);
+ int num = get_key_repeat();
+ if (num == -1)
+ return err_invalid_key(view);
+ if (num == 0)
+ num = 1;
view_wipe_selection(view);
view_wipe_line_cursor_attrs(view, view->cur_line);
- view_redo(view);
+ view_redo(view, num);
view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
finalize_repetition_stack();
return (struct action){ACTION_NONE, NULL};
@@ -2081,7 +2105,7 @@ dummy_cursor_helper(ledit_view *view, size_t line, size_t index, int num) {
static struct action \
name##_cb(ledit_view *view, char *text, size_t len) { \
motion_callback cb = NULL; \
- int num = get_key_repeat_and_motion_cb(&cb); \
+ int num = get_key_repeat_and_motion_cb(view, &cb); \
if (num == -1) \
return err_invalid_key(view); \
if (num == 0) \
@@ -2249,15 +2273,26 @@ repeat_command(ledit_view *view, char *text, size_t len) {
(void)view;
(void)text;
(void)len;
+ int num = get_key_repeat();
+ if (num == -1)
+ return err_invalid_key(view);
+ if (num == 0)
+ num = 1;
+ if (repetition_stack.len == 0) {
+ window_show_message(view->window, "No previous command", -1);
+ discard_repetition_stack();
+ return (struct action){ACTION_NONE, NULL};
+ }
int found;
repetition_stack.replaying = 1;
- clear_key_stack();
- unwind_repetition_stack();
- struct repetition_stack_elem *e = get_cur_repetition_stack_elem();
- while (e) {
- (void)handle_key(view, e->key_text, e->len, e->sym, e->key_state, e->lang_index, &found);
- advance_repetition_stack();
- e = get_cur_repetition_stack_elem();
+ for (int i = 0; i < num; i++) {
+ unwind_repetition_stack();
+ struct repetition_stack_elem *e = get_cur_repetition_stack_elem();
+ while (e) {
+ (void)handle_key(view, e->key_text, e->len, e->sym, e->key_state, e->lang_index, &found);
+ advance_repetition_stack();
+ e = get_cur_repetition_stack_elem();
+ }
}
repetition_stack.replaying = 0;
discard_repetition_stack();
@@ -2290,11 +2325,13 @@ basic_key_handler(ledit_view *view, XEvent *event, int lang_index) {
struct action act = handle_key(view, buf, (size_t)n, sym, key_state, lang_index, &found);
if (found && n > 0 && !view->window->message_shown)
window_hide_message(view->window);
- else
+ else if (msg_shown)
view->window->message_shown = msg_shown;
+ /* FIXME: add attribute for this to keys - this doesn't take e.g. cursor keys into account! */
if (found && n > 0)
view_ensure_cursor_shown(view);
- /* FIXME: maybe show error if not found */
+ if (!found && n > 0)
+ window_show_message(view->window, "Invalid key", -1);
return act;
}
diff --git a/keys_basic.h b/keys_basic.h
@@ -1,3 +1,11 @@
+#ifndef _KEYS_BASIC_H_
+#define _KEYS_BASIC_H_
+
+#include <X11/Xlib.h>
+#include "view.h"
+
/* perform cleanup of global data */
void basic_key_cleanup(void);
struct action basic_key_handler(ledit_view *view, XEvent *event, int lang_index);
+
+#endif
diff --git a/keys_basic_config.h b/keys_basic_config.h
@@ -20,7 +20,7 @@ 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 */
+ 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_view *, char *, size_t); /* callback function */
@@ -109,7 +109,7 @@ static struct key keys_en[] = {
{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, XK_ANY_MOD, XK_Return, INSERT, KEY_ANY, KEY_ANY, &return_key},
{NULL, 0, XK_Delete, INSERT, KEY_ANY, KEY_ANY, &delete_key},
{NULL, 0, XK_Escape, NORMAL|VISUAL|INSERT, KEY_ANY, KEY_ANY, &escape_key},
{"i", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_insert},
@@ -142,16 +142,16 @@ static struct key keys_en[] = {
{"c", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION|KEY_NUMBERALLOWED, &change},
{"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},
+ {"c", ControlMask, 0, 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},
+ {"?", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &enter_searchedit_backward},
+ {"/", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &enter_searchedit_forward},
+ {"n", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &key_search_next},
+ {"N", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &key_search_prev},
+ {"u", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &undo},
+ {"U", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &redo},
{".", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &repeat_command}, /* FIXME: only allow after finished key sequence */
{"z", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &undo},
{"y", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &redo}, /* FIXME: this is confusing with ctrl+y in normal mode */
diff --git a/keys_command.c b/keys_command.c
@@ -27,6 +27,7 @@
#include "util.h"
#include "keys.h"
+#include "keys_config.h"
#include "keys_command.h"
#include "keys_command_config.h"
@@ -57,9 +58,7 @@ history searchhistory = {0, 0, 0, NULL};
static void
push_history(history *hist, char *cmd, size_t len) {
if (hist->len >= hist->cap) {
- size_t cap = hist->cap * 2 > hist->cap + 2 ? hist->cap * 2 : hist->cap + 2;
- if (cap <= hist->len)
- exit(1); /* FIXME: overflow */
+ size_t cap = ideal_array_size(hist->cap, add_sz(hist->cap, 1));
hist->cmds = ledit_reallocarray(hist->cmds, cap, sizeof(char *));
hist->cap = cap;
}
@@ -100,15 +99,17 @@ view_locked_error(ledit_view *view) {
#define CHECK_VIEW_LOCKED(view) if (view->lock_text) return view_locked_error(view)
-/* FIXME: history for search and commands */
-
static int create_view(ledit_view *view, char *cmd, size_t l1, size_t l2);
static int close_view(ledit_view *view, char *cmd, size_t l1, size_t l2);
static int handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2);
static int handle_quit(ledit_view *view, char *cmd, size_t l1, size_t l2);
static int handle_write_quit(ledit_view *view, char *cmd, size_t l1, size_t l2);
static int handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2);
-static int parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid);
+static int parse_range(
+ ledit_view *view, char *cmd, size_t len, char **cmd_ret,
+ size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid,
+ char **errstr_ret
+);
static int handle_cmd(ledit_view *view, char *cmd, size_t len);
/* FIXME: remove command name before passing to handlers */
@@ -217,12 +218,7 @@ handle_write_quit(ledit_view *view, char *cmd, size_t l1, size_t l2) {
static void
show_num_substituted(ledit_view *view) {
- char buf[30];
- if (snprintf(buf, sizeof(buf), "%d substitution(s)", sub_state.num) < 0) {
- window_show_message(view->window, "This message is a bug, tell lumidify about it", -1);
- } else {
- window_show_message(view->window, buf, -1);
- }
+ window_show_message_fmt(view->window, "%d substitution(s)", sub_state.num);
}
/* returns 1 when match was found, 0 otherwise */
@@ -311,12 +307,11 @@ substitute_all_remaining(ledit_view *view) {
}
if (min_line < view->lines_num)
buffer_recalc_all_views_from_line(view->buffer, min_line);
- /* FIXME: show number replaced */
+ window_show_message_fmt(view->window, "Replaced %d occurrences", sub_state.num);
/* this doesn't need to be added to the undo stack since it's called on undo/redo anyways */
view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index);
view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
view_ensure_cursor_shown(view);
- show_num_substituted(view);
buffer_unlock_all_views(view->buffer);
}
@@ -369,6 +364,11 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) {
sub_state.num = 0;
sub_state.start_group = 1;
+ /* trying to perform substitution in visual mode would make
+ it unnecessarily complicated */
+ if (view->mode == VISUAL)
+ view_wipe_selection(view);
+ view_set_mode(view, NORMAL);
if (confirm) {
buffer_lock_all_views_except(view->buffer, view, "Ongoing substitution in other view.");
view->cur_command_type = CMD_SUBSTITUTE;
@@ -412,8 +412,12 @@ $ last line
/* NOTE: Marks are only recognized here if they are one unicode character! */
/* NOTE: Only the line range of the selection is used at the moment. */
static int
-parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid) {
+parse_range(
+ ledit_view *view, char *cmd, size_t len, char **cmd_ret,
+ size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid,
+ char **errstr_ret) {
(void)len;
+ *errstr_ret = "";
enum {
START_LINENO = 1,
START_RANGE = 2,
@@ -426,14 +430,22 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin
char *c = cmd;
while (*c != '\0') {
if (isdigit(*c)) {
- /* FIXME: integer overflow */
if (s & IN_LINENO) {
- if (*l2_valid) {
- l2 = l2 * 10 + (*c - '0');
- } else {
- l1 = l1 * 10 + (*c - '0');
+ size_t *final = &l2;
+ if (!*l2_valid) {
+ final = &l1;
*l1_valid = 1;
}
+ if (SIZE_MAX / 10 < *final) {
+ *errstr_ret = "Integer overflow in range";
+ return 1;
+ }
+ *final *= 10;
+ if (SIZE_MAX - (*c - '0') < *final) {
+ *errstr_ret = "Integer overflow in range";
+ return 1;
+ }
+ *final += (*c - '0');
} else if ((s & START_LINENO) && (s & START_RANGE)) {
l1 = *c - '0';
*l1_valid = 1;
@@ -444,8 +456,10 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin
s = IN_LINENO;
}
} else if (*c == '\'' && (s & START_LINENO)) {
- if (c[1] == '\0' || c[2] == '\0')
+ if (c[1] == '\0' || c[2] == '\0') {
+ *errstr_ret = "Invalid range";
return 1;
+ }
char *aftermark = next_utf8(c + 2);
size_t marklen = aftermark - (c + 1);
size_t l, b;
@@ -455,7 +469,7 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin
l = view->sel.line1 > view->sel.line2 ? view->sel.line1 : view->sel.line2;
} else {
if (buffer_get_mark(view->buffer, c + 1, marklen, &l, &b)) {
- /* FIXME: show better error message */
+ *errstr_ret = "Invalid mark";
return 1;
}
}
@@ -471,6 +485,7 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin
continue;
} else if (*c == ',' && !(s & START_RANGE)) {
if (*l1_valid && *l2_valid) {
+ *errstr_ret = "Invalid range";
return 1;
} else {
s = START_LINENO;
@@ -481,11 +496,13 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin
l2 = view->lines_num;
*l1_valid = *l2_valid = 1;
c++;
+ s = 0;
break;
} else {
+ *errstr_ret = "Invalid range";
return 1;
}
- } else if (*c == '$') {
+ } else if (*c == '.') {
if (s & START_LINENO) {
if (!*l1_valid) {
l1 = view->cur_line + 1;
@@ -496,6 +513,7 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin
}
s = 0;
} else {
+ *errstr_ret = "Invalid range";
return 1;
}
} else if (*c == '$') {
@@ -509,6 +527,7 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin
}
s = 0;
} else {
+ *errstr_ret = "Invalid range";
return 1;
}
} else {
@@ -516,10 +535,14 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin
}
c++;
}
- if ((!*l1_valid || !*l2_valid) && !(s & START_RANGE))
+ if ((!*l1_valid || !*l2_valid) && !(s & START_RANGE)) {
+ *errstr_ret = "Invalid range";
return 1;
- if ((*l1_valid || *l2_valid) && (l1 == 0 || l2 == 0 || l1 > view->lines_num || l2 > view->lines_num))
- return 1; /* FIXME: better error messages */
+ }
+ if ((*l1_valid || *l2_valid) && (l1 == 0 || l2 == 0 || l1 > view->lines_num || l2 > view->lines_num)) {
+ *errstr_ret = "Invalid line number in range";
+ return 1;
+ }
*cmd_ret = c;
/* ranges are given 1-indexed by user */
*line1_ret = l1 - 1;
@@ -535,8 +558,9 @@ handle_cmd(ledit_view *view, char *cmd, size_t len) {
char *c;
size_t l1, l2;
int l1_valid, l2_valid;
- if (parse_range(view, cmd, len, &c, &l1, &l2, &l1_valid, &l2_valid)) {
- window_show_message(view->window, "Error parsing command", -1);
+ char *errstr;
+ if (parse_range(view, cmd, len, &c, &l1, &l2, &l1_valid, &l2_valid, &errstr)) {
+ window_show_message(view->window, errstr, -1);
return 0;
}
int range_given = l1_valid && l2_valid;
@@ -743,7 +767,7 @@ edit_nextsearch(ledit_view *view, char *key_text, size_t len) {
void
search_next(ledit_view *view) {
view_wipe_line_cursor_attrs(view, view->cur_line);
- enum ledit_search_state ret = ledit_search_next(view, &view->cur_line, &view->cur_index);
+ search_state ret = ledit_search_next(view, &view->cur_line, &view->cur_index);
view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
view_ensure_cursor_shown(view);
if (ret != SEARCH_NORMAL)
@@ -753,7 +777,7 @@ search_next(ledit_view *view) {
void
search_prev(ledit_view *view) {
view_wipe_line_cursor_attrs(view, view->cur_line);
- enum ledit_search_state ret = ledit_search_prev(view, &view->cur_line, &view->cur_index);
+ search_state ret = ledit_search_prev(view, &view->cur_line, &view->cur_index);
view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
view_ensure_cursor_shown(view);
if (ret != SEARCH_NORMAL)
diff --git a/keys_command.h b/keys_command.h
@@ -1,6 +1,14 @@
+#ifndef _KEYS_COMMAND_H_
+#define _KEYS_COMMAND_H_
+
+#include <X11/Xlib.h>
+#include "view.h"
+
/* these are only here so they can also be used by keys_basic */
void search_next(ledit_view *view);
void search_prev(ledit_view *view);
void command_key_cleanup(void);
struct action command_key_handler(ledit_view *view, XEvent *event, int lang_index);
+
+#endif
diff --git a/keys_command_config.h b/keys_command_config.h
@@ -38,9 +38,9 @@ static struct key keys_en[] = {
{"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},
+ {NULL, XK_ANY_MOD, XK_Return, CMD_EDIT, &edit_submit},
+ {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCH, &editsearch_submit},
+ {NULL, XK_ANY_MOD, XK_Return, CMD_EDITSEARCHB, &editsearchb_submit},
{NULL, 0, XK_Left, CMD_EDIT, &edit_cursor_left},
{NULL, 0, XK_Left, CMD_EDITSEARCH, &edit_cursor_left},
{NULL, 0, XK_Left, CMD_EDITSEARCHB, &edit_cursor_left},
diff --git a/keys_config.h b/keys_config.h
@@ -0,0 +1,31 @@
+#ifndef _KEYS_CONFIG_H_
+#define _KEYS_CONFIG_H_
+
+#include "keys.h"
+
+/*
+ * These are the language strings compared with the language strings that
+ * xkb gives in order to change the key mapping on layout change events.
+ */
+#define KEY_LANGS \
+static char *key_langs[] = { \
+ "English (US)", \
+ "German", \
+ "Urdu (Pakistan)", \
+ "Hindi (Bolnagri)" \
+}
+
+#define GEN_KEY_ARRAY(key_struct, en, de, ur, hi) \
+static struct { \
+ key_struct *keys; \
+ int num_keys; \
+} keys[] = { \
+ {en, LENGTH(en)}, \
+ {de, LENGTH(de)}, \
+ {ur, LENGTH(ur)}, \
+ {hi, LENGTH(hi)} \
+}
+
+#define LANG_KEYS(index) &keys[index]
+
+#endif
diff --git a/ledit.1 b/ledit.1
@@ -0,0 +1,796 @@
+.\" WARNING: Some parts of this are stolen shamelessly from OpenBSD's
+.\" vi(1) manpage!
+.Dd December 18, 2021
+.Dt LEDIT 1
+.Os
+.Sh NAME
+.Nm ledit
+.Nd weird text editor
+.Sh SYNOPSIS
+.Nm
+.Op Ar file
+.Sh DESCRIPTION
+.Nm
+is a vi-like text editor for people who switch between keyboard layouts
+frequently and/or work with languages that require complex text layout.
+.Pp
+It is assumed that readers of this manual page are already familiar
+with
+.Xr vi 1 .
+Differences with
+.Xr vi 1
+are documented, but it is very likely that many have been missed.
+If you find an important difference that is not documented, please
+contact me.
+.Sh ANTI-DESCRIPTION
+.Bl -tag -width Ds
+.It Nm
+is not a code editor.
+Features for code editing may be added in the future, but the main
+purpose is currently to edit other text.
+.It Nm
+is not a general-purpose text editor.
+It is content to be useful for some tasks and does not feel insulted when
+other editors are used for other tasks.
+.It Nm
+is not a minimalistic text editor.
+It probably counts as reasonably minimalistic in the modern world, but
+that is not the main goal.
+.It Nm
+is not a good text editor.
+.El
+.Sh KEY BINDINGS
+The key bindings listed here are given as the default English bindings.
+These will, of course, not be accurate for other languages or if
+the configuration is changed.
+.Pp
+Some commands change their behavior depending on whether the mode is set
+to soft line or hard line.
+This is sometimes a bit inconsistent, but at least it might help when
+editing longer paragraphs with no manual line breaking.
+.Pp
+Note that this list is currently not sorted in any logical way.
+That will hopefully be fixed in the future.
+.Ss NORMAL MODE
+.Bl -tag -width Ds -compact
+.It Xo
+.Op Ar count
+.Aq Cm arrow down
+.Xc
+.It Xo
+.Op Ar count
+.Aq Cm control-j
+.Xc
+.It Xo
+.Op Ar count
+.Aq Cm control-n
+.Xc
+.It Xo
+.Op Ar count
+.Cm j
+.Xc
+Move the cursor down
+.Ar count
+lines.
+This changes behavior depending on the hard/soft line mode.
+.Pp
+.It Xo
+.Op Ar count
+.Aq Cm arrow up
+.Xc
+.It Xo
+.Op Ar count
+.Aq Cm control-p
+.Xc
+.It Xo
+.Op Ar count
+.Cm k
+.Xc
+Move the cursor up
+.Ar count
+lines.
+This changes behavior depending on the hard/soft line mode.
+.Pp
+.It Xo
+.Op Ar count
+.Aq Cm arrow right
+.Xc
+.It Xo
+.Op Ar count
+.Aq Cm space
+.Xc
+.It Xo
+.Op Ar count
+.Cm l
+.Xc
+Move the cursor right
+.Ar count
+characters in the current line.
+Note that this is a visual operation, i.e. the cursor will still move right
+if the text is right-to-left.
+.Pp
+.It Xo
+.Op Ar count
+.Aq Cm arrow left
+.Xc
+.It Xo
+.Op Ar count
+.Aq Cm control-h
+.Xc
+.It Xo
+.Op Ar count
+.Cm h
+.Xc
+Move the cursor left
+.Ar count
+characters in the current line.
+Note that this is a visual operation, i.e. the cursor will still move left
+if the text is right-to-left.
+.Pp
+.It Aq Cm escape
+Clear the key stack (i.e. cancel multi-key command).
+.Pp
+.It Cm i
+Enter insert mode.
+.Pp
+.It Cm v
+Enter visual mode.
+.Pp
+.It Aq Cm control-t
+Toggle mode between hard line and soft line based.
+.Pp
+.It Cm 0
+Move cursor to beginning of line.
+This changes behavior depending on the hard/soft line mode.
+.Pp
+.It Xo
+.Op Ar count
+.Cm x
+.Xc
+Delete
+.Ar count
+characters after the cursor on the current line and copy the
+deleted text into the paste buffer.
+.Pp
+.It Xo
+.Op Ar count
+.Cm X
+.Xc
+Delete
+.Ar count
+characters before the cursor on the current line and copy the
+deleted text into the paste buffer.
+.Pp
+.It Xo
+.Op Ar count
+.Cm d
+.Ar motion
+.Xc
+Delete the region of text described by
+.Ar count
+and
+.Ar motion
+and copy the deleted text into the paste buffer.
+If
+.Ar motion
+is
+.Cm d
+again,
+.Ar count
+lines are deleted, starting with the current line.
+This changes behavior depending on the hard/soft line mode.
+.Pp
+.It Cm D
+Delete all text from the current cursor position to the end of
+the line and copy the deleted text into the paste buffer.
+This changes behavior depending on the hard/soft line mode.
+.Pp
+.It Xo
+.Op Ar count
+.Cm y
+.Ar motion
+.Xc
+Copy the region of text described by
+.Ar count
+and
+.Ar motion
+into the paste buffer.
+If
+.Ar motion
+is
+.Cm y
+again,
+.Ar count
+lines are copied, starting with the current line.
+This changes behavior depending on the hard/soft line mode.
+.Pp
+.It Xo
+.Op Ar count
+.Cm Y
+.Xc
+Copy
+.Ar count
+lines into the paste buffer.
+This changes behavior depending on the hard/soft line mode.
+.Pp
+.It Xo
+.Op Ar count
+.Cm c
+.Ar motion
+.Xc
+Change the region described by
+.Ar count
+and
+.Ar motion
+and copy the changed text into the paste buffer.
+.Pp
+.It Cm C
+Change all text from the current cursor position to the end of
+the line and copy the changed text into the paste buffer.
+This changes behavior depending on the hard/soft line mode.
+.Pp
+.It Aq Cm control-g
+Show the current filename, whether the buffer has been modified since the
+last write, and the current line.
+.Pp
+.It Cm \&:
+Enter the line-editing mode for running a command.
+.Pp
+.It Cm /
+Search forward for a search term.
+Note that no regex is currently supported.
+.Pp
+.It Cm \&?
+Search backwards for a search term.
+Note that no regex is currently supported.
+.Pp
+.It Cm n
+Move to the next match of the last search term in the direction of the
+last search.
+.Pp
+.It Cm N
+Move to the next match of the last search term in the direction opposite
+to the direction of the last search.
+.Pp
+.It Xo
+.Op Ar count
+.Cm u
+.Xc
+Undo
+.Ar count
+operations.
+Note that an entire session in insert mode is considered as one operation
+when in normal mode.
+.Pp
+.It Xo
+.Op Ar count
+.Cm U
+.Xc
+Redo
+.Ar count
+operations.
+Note that an entire session in insert mode is considered as one operation
+when in normal mode.
+.Pp
+.It Xo
+.Op Ar count
+.Cm \&.
+.Xc
+Repeat the last command
+.Ar count
+times.
+.Pp
+.It Xo
+.Op Ar count
+.Aq Cm control-b
+.Xc
+Move
+.Ar count
+screens up.
+.Pp
+.It Xo
+.Op Ar count
+.Aq Cm control-f
+.Xc
+Move
+.Ar count
+screens down.
+.Pp
+.It Xo
+.Op Ar count
+.Aq Cm control-y
+.Xc
+Move
+.Ar count
+lines up, attemting to leave the cursor in its current line and
+character position.
+Note that this command works with soft lines, regardless of the
+current mode.
+.Pp
+.It Xo
+.Op Ar count
+.Aq Cm control-e
+.Xc
+Move
+.Ar count
+lines down, attemting to leave the cursor in its current line and
+character position.
+Note that this command works with soft lines, regardless of the
+current mode.
+.Pp
+.It Xo
+.Op Ar count
+.Aq Cm control-u
+.Xc
+Move
+.Ar count
+lines up.
+If
+.Ar count
+is not given, scroll up the number of lines specified by the last
+.Aq Cm control-d
+or
+.Aq Cm control-u
+command.
+If this is the first such command, scroll up half a screen.
+Note that this command works with soft lines, regardless of the
+current mode.
+.Pp
+.It Xo
+.Op Ar count
+.Aq Cm control-d
+.Xc
+Move
+.Ar count
+lines down.
+If
+.Ar count
+is not given, scroll down the number of lines specified by the last
+.Aq Cm control-d
+or
+.Aq Cm control-u
+command.
+If this is the first such command, scroll down half a screen.
+Note that this command works with soft lines, regardless of the
+current mode.
+.Pp
+.It Cm $
+Move to the last cursor position on the current line.
+This changes behavior depending on the hard/soft line mode.
+.Pp
+.It Xo
+.Op Ar count
+.Cm w
+.Xc
+Move forward
+.Ar count
+words.
+.Pp
+.It Xo
+.Op Ar count
+.Cm W
+.Xc
+Move forward
+.Ar count
+bigwords.
+.Pp
+.It Xo
+.Op Ar count
+.Cm e
+.Xc
+Move forward
+.Ar count
+end-of-words.
+.Pp
+.It Xo
+.Op Ar count
+.Cm E
+.Xc
+Move forward
+.Ar count
+end-of-bigwords.
+.Pp
+.It Xo
+.Op Ar count
+.Cm b
+.Xc
+Move backwards
+.Ar count
+words.
+.Pp
+.It Xo
+.Op Ar count
+.Cm B
+.Xc
+Move backwards
+.Ar count
+bigwords.
+.Pp
+.It Xo
+.Op Ar count
+.Cm G
+.Xc
+Move to the line number given by
+.Ar count .
+If
+.Ar count
+is not given, move to the last line in the buffer.
+.Pp
+.It Xo
+.Op Ar count
+.Cm J
+.Xc
+Join the current line with the next one
+.Ar count
+times.
+Note that this command always works on hard lines, regardless
+of the current mode.
+Also note that this currently does not compress whitespace between
+the lines as other vi-like editors do.
+This is due to the author's laziness.
+.Pp
+.It Cm I
+Move cursor to beginning of line and enter insert mode.
+This changes behavior depending on the hard/soft line mode.
+.Pp
+.It Cm p
+Paste text from the paste buffer after the current cursor position if the
+buffer is character-based and after the current line if it is line-based.
+Note that this does take into account the hard line/soft line mode, but
+it behaves a bit weirdly when in soft line mode - it inserts the text
+after the current soft line but adds newlines on both sides.
+This behavior may be changed in the future if it turns out there's a more
+logical behavior for soft line mode.
+.Pp
+.It Cm P
+Paste text from the paste buffer before the current cursor position if the
+buffer is character-based and before the current line if it is line-based.
+The quirk for
+.Cm p
+applies here as well.
+.Pp
+.It Cm A
+Append text after the current line.
+This changes behavior depending on the hard/soft line mode.
+.Pp
+.It Cm a
+Append text after the current cursor position.
+.Pp
+.It Cm o
+Append a new line after the current line and enter insert mode there.
+This changes behavior depending on the hard/soft line mode.
+.Pp
+.It Cm O
+Append a new line before the current line and enter insert mode there.
+This changes behavior depending on the hard/soft line mode.
+.Pp
+.It Xo
+.Cm m
+.Aq Cm character
+.Xc
+Mark the current current cursor position as
+.Aq Cm character .
+.Pp
+.It Xo
+.Cm '
+.Aq Cm character
+.Xc
+Jump to a position previously marked as
+.Aq Cm character
+with
+.Cm m .
+.Pp
+.It Xo
+.Cm r
+.Aq Cm character
+.Xc
+Replace the character at the current cursor position with
+.Aq Cm character .
+.Pp
+.It Cm ^
+Move to the first non-whitespace character on the current line.
+This changes behavior depending on the hard/soft line mode.
+.Pp
+.It Xo
+.Op Ar count
+.Cm t
+.Aq Cm character
+.Xc
+Search forward,
+.Op Ar count
+times, through the current line for the cursor position immediately before
+.Aq Cm character .
+.Pp
+.It Xo
+.Op Ar count
+.Cm T
+.Aq Cm character
+.Xc
+Search backwards,
+.Op Ar count
+times, through the current line for the cursor position after
+.Aq Cm character .
+.Pp
+.It Xo
+.Op Ar count
+.Cm f
+.Aq Cm character
+.Xc
+Search forward,
+.Op Ar count
+times, through the current line for
+.Aq Cm character .
+.Pp
+.It Xo
+.Op Ar count
+.Cm F
+.Aq Cm character
+.Xc
+Search backwards,
+.Op Ar count
+times, through the current line for
+.Aq Cm character .
+.El
+.Ss VISUAL MODE
+The movement keys generally work the same in visual mode but change the
+selection instead of just moving to the new position, so they are not
+listed here separately.
+.Pp
+The
+.Cm d ,
+.Cm y ,
+and
+.Cm c
+keys also work similarly, but operate on the range given by the selection.
+.Pp
+The
+.Cm \&:
+key automatically pastes the selection range into the line editor so a
+command can be run over the range specified by the selection.
+.Pp
+Additionally, these keys are supported:
+.Pp
+.Bl -tag -width Ds -compact
+.It Cm o
+Switch the end of the selection that is modified by the movement keys.
+.Pp
+.It Aq Cm control-c
+Copy the current selection to the clipboard.
+.Pp
+.It Aq Cm escape
+Return to normal mode.
+.El
+.Ss INSERT MODE
+All regular keys simply insert the corresponding text at the current cursor
+position.
+.Pp
+The cursor keys, backspace, delete, and return all work as expected.
+.Pp
+Additionally, the following keys are supported:
+.Pp
+.Bl -tag -width Ds -compact
+.It Aq Cm control-v
+Paste text from the clipboard at the current cursor position.
+.Pp
+.It Aq Cm control-z
+Undo one operation.
+Note that this, in contrast to the undo in normal mode, does not consider
+an entire insert session to be one operation.
+.Pp
+.It Aq Cm control-y
+Redo one operation.
+Note that this, in contrast to the redo in normal mode, does not consider
+an entire insert session to be one operation.
+.Pp
+.It Aq Cm escape
+Return to normal mode.
+.El
+Note that many keys that are common in other editors are not recognized currently.
+That will hopefully be fixed in the future.
+.Ss LINE EDITING MODE
+These key bindings work in the line editor that is used for searching or
+running commands.
+.Pp
+.Bl -tag -width DS -compact
+.It Aq Cm return
+Submit the search or command.
+.Pp
+.It Aq Cm arrow left
+Move the cursor one to the left.
+.Pp
+.It Aq Cm arrow right
+Move the cursor one to the right.
+.Pp
+.It Aq Cm arrow up
+.It Aq Cm arrow down
+Move through the search or command history.
+Note that the search and command histories are separate.
+.Pp
+.It Aq Cm backspace
+Delete one unicode character before the cursor.
+.Pp
+.It Aq Cm delete
+Delete one unicode character after the cursor.
+.Pp
+.It Aq Cm end
+Move the cursor to the end of the line.
+.Pp
+.It Aq Cm home
+Move the cursor to the beginning of the line.
+.Pp
+.It Aq Cm escape
+Cancel the search or command.
+.El
+.Ss MISCELLANEOUS
+.Bl -tag -width DS
+.It Keys while performing substitution with confirmation:
+.Bl -tag -width Ds
+.It Cm y
+Confirm the current substitution.
+.It Cm n
+Reject the current substitution.
+.It Cm Y
+Confirm the current substitution and all further ones.
+.It Cm N
+Reject the current substitution and all further ones.
+.El
+.Pp
+Note that these keys are also displayed during the substitution, but only
+the default English bindings are shown because implementing anything else
+would require work.
+.El
+.Sh COMMANDS
+Note: The terminology is currently a bit inconsistent.
+Sometimes,
+.Dq commands
+refers to the key commands, sometimes to the commands
+written in the line editor, which are documented in this section.
+.Pp
+Note that the commands which take filenames currently use the entire rest of
+the line as the filename instead of doing any string parsing.
+This may be changed in the future.
+.Pp
+.Bl -tag -width Ds -compact
+.It Xo
+.Cm :w
+.Op Ar filename
+.Xc
+Write the buffer to
+.Op Ar filename ,
+or, if no filename is given, to the file the buffer was read from.
+.Pp
+.It Xo
+.Cm :w\&!
+.Op Ar filename
+.Xc
+Same as
+.Cm :w ,
+but the file will be attempted to be written to even if there
+is something blocking it (e.g. the modified date of the file is newer
+than it was when it was opened).
+.Pp
+.It Cm :q
+Quit.
+.Pp
+.It Cm :q\&!
+Quit, even if there are unsaved changes.
+.Pp
+.It Xo
+.Cm :wq
+.Op Ar filename
+.Xc
+.It Xo
+.Cm :wq\&!
+.Op Ar filename
+.Xc
+Write and quit afterwards.
+The
+.Cm \&!
+is interpreted as for normal writing.
+.Pp
+.It Xo
+.Sm off
+.Op Ar range
+.Cm s / Ar pattern Cm / Ar replace Cm /
+.Op Ar options
+.Sm on
+.Xc
+Substitute
+.Ar pattern
+with
+.Ar replace
+in the given line range.
+If no range is given, substitution is only performed on the current line.
+Note that no regex is currently supported.
+.Pp
+The range consists of two line numbers separated by a comma or the special value
+.Cm % ,
+which refers to the entire file.
+The following special values are possible instead of writing a line number directly:
+.Bl -tag -width Ds
+.It Cm $
+The last line in the file.
+.It Xo
+.Sm off
+.Cm ' Aq Cm mark
+.Sm on
+.Xc
+The line of the previously set mark
+.Aq Cm mark .
+Note that even though marks can theoretically be any string of characters,
+they are only allowed to be one unicode character if they are used in a range.
+The special values
+.Cm <
+and
+.Cm >
+are possible, which refer to the first and last line, respectively, in the
+current selection.
+.It Cm \&.
+The current line.
+.El
+.Pp
+The
+.Ar options
+may be a combination of the following:
+.Bl -tag -width Ds
+.It Cm g
+Perform substitution for all occurrences in the given lines instead of just
+the first one on each line.
+.It Cm c
+Confirm each substitution before performing it.
+.El
+.Pp
+.It Cm :v
+Open a new view.
+Each view is a window that shows the text in the current buffer,
+which is synced between the views.
+.El
+.Sh MOUSE ACTIONS
+There currently are not many mouse actions.
+Clicking and dragging with the left mouse button enters visual mode and
+selects text, which is always copied into the X11 primary selection.
+.Pp
+Note that text selection currently does not work in the line editor
+because the author is too lazy to implement that.
+.Sh CONFIGURATION
+(Todo) - also document weirdness with xkb language strings
+.Sh MISCELLANEOUS
+(Todo) - document emergency dumps
+.Sh EXIT STATUS
+.Ex -std
+.Sh QUIRKS
+The cursor movement commands try to move left/right visually instead of moving
+through the text logically.
+This causes weird cursor jumps when working with bidirectional text in normal mode.
+This may be fixed in the future, but it currently is not clear how to make the
+behavior more logical.
+.Pp
+Since a new mode group is started each time insert is entered, when text
+is typed in one view in insert, then in another view, and then again in
+the first one, the last two inserts will be undone in one go since both
+views were in insert already.
+I'm not sure how to make this more logical, though.
+Maybe it could be
+.Dq improved
+by also saving the view in the undo stack,
+but that would cause problems because views can be added and removed,
+and it would maybe not even be more logical.
+.Pp
+Scroll offset is stored as a pixel value, so a view may scroll when text is
+added or deleted in another view.
+Additionally, when a new view is created, the scroll offset from the old view
+is taken, which may be weird if the window of the new view is a different size.
+.Pp
+(Todo) - document weirdness with spaces at end of line in normal mode
+.Sh SEE ALSO
+.Xr ed 1 ,
+.Xr vi 1 ,
+.Xr vim 1
+.Sh AUTHORS
+.An lumidify Aq Mt nobody@lumidify.org
+.Sh BUGS
+Too many to count.
+See
+.Sx TINY SUBSET OF BUGS .
+.Sh TINY SUBSET OF BUGS
+(Todo)
diff --git a/ledit.c b/ledit.c
@@ -1,4 +1,3 @@
-/* FIXME: clean up asserts a bit; clean up includes */
/* FIXME: On large files, expose event takes a long time for some reason
-> but somehow only sometimes... */
/* FIXME: generally optimize redrawing */
@@ -7,50 +6,38 @@
/* 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 */
-/* FIXME: Fix lag when selecting with mouse */
/* FIXME: Use PANGO_PIXELS() */
/* FIXME: Fix cursor movement, especially buffer->trailing and writing at end of line */
/* FIXME: horizontal scrolling (also need cache to avoid too large pixmaps) */
-/* FIXME: sort out types for indices (currently just int, but that might overflow) */
/* TODO: allow extending selection with shift+mouse like in e.g. gtk */
-#include <math.h>
+
#include <time.h>
-#include <stdio.h>
#include <errno.h>
-#include <string.h>
+#include <stdio.h>
#include <stdlib.h>
-#include <limits.h>
-#include <unistd.h>
+#include <string.h>
#include <locale.h>
+#include <unistd.h>
+#include <sys/stat.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 <X11/extensions/XKBrules.h>
-#include "config.h"
-#include "memory.h"
-#include "common.h"
-#include "txtbuf.h"
+#include "view.h"
#include "theme.h"
-#include "window.h"
-#include "cache.h"
-#include "undo.h"
#include "buffer.h"
-#include "view.h"
+#include "common.h"
+#include "window.h"
#include "search.h"
+#include "macros.h"
+#include "memory.h"
+#include "config.h"
+#include "cleanup.h"
#include "keys.h"
#include "keys_basic.h"
#include "keys_command.h"
-#include "macros.h"
-#include "cleanup.h"
static void mainloop(void);
static void setup(int argc, char *argv[]);
@@ -326,6 +313,12 @@ ledit_emergencydump(void) {
/* FIXME: maybe just leave the file in case at
least part of it was written? */
unlink(template);
+ } else {
+ fprintf(
+ stderr,
+ "Wrote emergency dump to %s\n",
+ template
+ );
}
free(template);
}
diff --git a/macros.h b/macros.h
@@ -1,3 +1,6 @@
+#ifndef _MACROS_H_
+#define _MACROS_H_
+
/* stolen from OpenBSD */
#define ledit_timespecsub(tsp, usp, vsp) \
do { \
@@ -8,3 +11,5 @@
(vsp)->tv_nsec += 1000000000L; \
} \
} while (0)
+
+#endif
diff --git a/memory.c b/memory.c
@@ -3,8 +3,9 @@
#include <stdlib.h>
#include <string.h>
-#include "cleanup.h"
#include "assert.h"
+#include "memory.h"
+#include "cleanup.h"
static void
fatal_err(const char *msg) {
@@ -15,14 +16,18 @@ fatal_err(const char *msg) {
void
err_overflow(void) {
- fprintf(stderr, "Integer overflow.\n");
- ledit_cleanup();
- exit(1);
+ (void)fprintf(stderr, "Integer overflow.\n");
+ ledit_emergencydump();
+ abort();
}
+/* FIXME: should these perform emergencydump instead of just
+ fatal_err? It probably isn't of much use when there isn't
+ even any memory left. */
char *
ledit_strdup(const char *s) {
char *str = strdup(s);
+ ledit_assert(str && "Out of memory.");
if (!str)
fatal_err("Out of memory.\n");
return str;
@@ -93,7 +98,7 @@ ledit_reallocarray(void *optr, size_t nmemb, size_t size)
{
if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
nmemb > 0 && SIZE_MAX / nmemb < size) {
- fatal_err("Integer overflow in reallocarray.\n");
+ err_overflow();
}
return realloc(optr, size * nmemb);
}
@@ -141,57 +146,76 @@ resize_and_move_gap(
size_t *new_gap_ret, size_t *new_cap_ret) {
ledit_assert(index <= len);
ledit_assert(len <= old_cap);
+ ledit_assert(old_gap <= len);
size_t gap_size = old_cap - len;
- size_t new_cap = old_cap;
- /* FIXME: read up on what the best values are here */
- if (new_cap < min_size)
- new_cap = old_cap * 2 > min_size ? old_cap * 2 : min_size;
- if (new_cap < min_size)
- err_overflow();
- if (new_cap != old_cap)
- array = ledit_reallocarray(array, new_cap, elem_size);
- char *carray = (char*)array; /* cast to char to do pointer arithmetic */
- /* we already know new_cap * elem_size does not wrap around because array
- is of that size, so all the other multiplications here should be safe
- (at least that's what I think, but I may be wrong) */
- if (index > old_gap) {
- /* move piece between end of original gap and index to
- beginning of original gap */
- memmove(
- carray + old_gap * elem_size,
- carray + (old_gap + gap_size) * elem_size,
- (index - old_gap) * elem_size
- );
- /* move piece after index to end of buffer */
- memmove(
- carray + (new_cap - (len - index)) * elem_size,
- carray + (index + gap_size) * elem_size,
- (len - index) * elem_size
- );
- } else if (index < old_gap) {
- /* move piece after original gap to end of buffer */
- memmove(
- carray + (new_cap - (len - old_gap)) * elem_size,
- carray + (old_gap + gap_size) * elem_size,
- (len - old_gap) * elem_size
- );
- /* move piece between index and original gap to end */
- memmove(
- carray + (new_cap - len + index) * elem_size,
- carray + index * elem_size,
- (old_gap - index) * elem_size
- );
+ size_t new_cap = ideal_array_size(old_cap, min_size);;
+ if (new_cap >= old_cap) {
+ if (new_cap > old_cap)
+ array = ledit_reallocarray(array, new_cap, elem_size);
+ char *carray = (char*)array; /* cast to char to do pointer arithmetic */
+ /* we already know new_cap * elem_size does not wrap around because array
+ is of that size, so all the other multiplications here should be safe
+ (at least that's what I think, but I may be wrong) */
+ if (index > old_gap) {
+ /* move piece between end of original gap and index to
+ beginning of original gap */
+ memmove(
+ carray + old_gap * elem_size,
+ carray + (old_gap + gap_size) * elem_size,
+ (index - old_gap) * elem_size
+ );
+ /* move piece after index to end of buffer */
+ memmove(
+ carray + (new_cap - (len - index)) * elem_size,
+ carray + (index + gap_size) * elem_size,
+ (len - index) * elem_size
+ );
+ } else if (index < old_gap) {
+ /* move piece after original gap to end of buffer */
+ memmove(
+ carray + (new_cap - (len - old_gap)) * elem_size,
+ carray + (old_gap + gap_size) * elem_size,
+ (len - old_gap) * elem_size
+ );
+ /* move piece between index and original gap to end */
+ memmove(
+ carray + (new_cap - len + index) * elem_size,
+ carray + index * elem_size,
+ (old_gap - index) * elem_size
+ );
+ } else {
+ /* move piece after original gap to end of buffer */
+ memmove(
+ carray + (new_cap - (len - old_gap)) * elem_size,
+ carray + (old_gap + gap_size) * elem_size,
+ (len - old_gap) * elem_size
+ );
+ }
} else {
- /* move piece after original gap to end of buffer */
- memmove(
- carray + (new_cap - (len - old_gap)) * elem_size,
- carray + (old_gap + gap_size) * elem_size,
- (len - old_gap) * elem_size
- );
+ /* otherwise, parts may be cut off */
+ ledit_assert(min_size >= len);
+ /* FIXME: optimize this */
+ move_gap(array, elem_size, len, old_gap, old_cap, len, NULL);
+ array = ledit_reallocarray(array, new_cap, elem_size);
+ move_gap(array, elem_size, index, len, new_cap, len, NULL);
}
if (new_gap_ret)
*new_gap_ret = index;
if (new_cap_ret)
*new_cap_ret = new_cap;
- return carray;
+ return array;
+}
+
+/* FIXME: maybe don't double when already very large? */
+/* FIXME: better start size when old == 0? */
+size_t
+ideal_array_size(size_t old, size_t needed) {
+ size_t ret = old;
+ if (old < needed)
+ ret = old * 2 > needed ? old * 2 : needed;
+ else if (needed * 4 < old)
+ ret = old / 2;
+ if (ret == 0)
+ ret = 1; /* not sure if this is necessary */
+ return ret;
}
diff --git a/memory.h b/memory.h
@@ -1,3 +1,9 @@
+#ifndef _MEMORY_H_
+#define _MEMORY_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
/*
* These functions all wrap the regular functions but exit on error.
*/
@@ -28,6 +34,7 @@ void move_gap(
/*
* Resize a generic gap buffer with elements of size 'elem_size' to hold at least
* 'min_size' elements and move the gap to element position 'index'.
+ * The array size may be increased or decreased.
* 'old_gap' is the old index of the gap buffer, 'old_cap' is the total length of the
* array (number of elements, not bytes), and 'len' is the number of valid elements.
* 'index' is also written to 'new_gap_ret' if it is not NULL. This is just
@@ -45,3 +52,12 @@ void *resize_and_move_gap(
/* FIXME: not sure if this really belongs here */
void err_overflow(void);
+
+/*
+ * Return the ideal new size for an array of size 'old' when resizing it
+ * so it fits at least 'needed' elements. The return value may be smaller
+ * than 'old' if 'needed' is smaller.
+ */
+size_t ideal_array_size(size_t old, size_t needed);
+
+#endif
diff --git a/pango-compat.h b/pango-compat.h
@@ -1,4 +1,11 @@
+#ifndef _PANGO_COMPAT_H_
+#define _PANGO_COMPAT_H_
+
+#include <pango/pangoxft.h>
+
//#if !PANGO_VERSION_CHECK(1, 46, 0)
#if 1
PangoDirection ledit_pango_layout_get_direction(PangoLayout *layout, int index);
#endif
+
+#endif
diff --git a/search.c b/search.c
@@ -1,22 +1,9 @@
-/* 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>
-#include <X11/extensions/Xdbe.h>
-
-#include "memory.h"
-#include "common.h"
-#include "txtbuf.h"
-#include "undo.h"
-#include "cache.h"
-#include "theme.h"
-#include "window.h"
+#include "view.h"
#include "buffer.h"
#include "search.h"
+#include "memory.h"
/* FIXME: make sure only whole utf8 chars are matched */
static char *last_search = NULL;
@@ -44,7 +31,7 @@ set_search_backward(char *pattern) {
last_search = ledit_strdup(pattern);
}
-static enum ledit_search_state
+static search_state
search_forward(ledit_view *view, size_t *line_ret, size_t *byte_ret) {
*line_ret = view->cur_line;
*byte_ret = view->cur_index;
@@ -93,7 +80,7 @@ search_forward(ledit_view *view, size_t *line_ret, size_t *byte_ret) {
/* FIXME: this is insanely inefficient */
/* FIXME: just go backwards char-by-char and compare */
-static enum ledit_search_state
+static search_state
search_backward(ledit_view *view, size_t *line_ret, size_t *byte_ret) {
*line_ret = view->cur_line;
*byte_ret = view->cur_index;
@@ -157,7 +144,7 @@ search_backward(ledit_view *view, size_t *line_ret, size_t *byte_ret) {
return SEARCH_NOT_FOUND;
}
-enum ledit_search_state
+search_state
ledit_search_next(ledit_view *view, size_t *line_ret, size_t *byte_ret) {
if (last_dir == FORWARD)
return search_forward(view, line_ret, byte_ret);
@@ -165,7 +152,7 @@ ledit_search_next(ledit_view *view, size_t *line_ret, size_t *byte_ret) {
return search_backward(view, line_ret, byte_ret);
}
-enum ledit_search_state
+search_state
ledit_search_prev(ledit_view *view, size_t *line_ret, size_t *byte_ret) {
if (last_dir == FORWARD)
return search_backward(view, line_ret, byte_ret);
@@ -174,16 +161,18 @@ ledit_search_prev(ledit_view *view, size_t *line_ret, size_t *byte_ret) {
}
char *
-search_state_to_str(enum ledit_search_state state) {
- switch (state) {
- case SEARCH_WRAPPED:
- return "Search wrapped";
- case SEARCH_NOT_FOUND:
- return "Pattern not found";
- case SEARCH_NO_PATTERN:
- return "No previous search pattern";
- default:
- return "This message should not be shown. "
- "Please bug lumidify about it.";
+search_state_to_str(search_state s) {
+ switch (s) {
+ case SEARCH_NORMAL:
+ return "Found match";
+ case SEARCH_WRAPPED:
+ return "Search wrapped";
+ case SEARCH_NOT_FOUND:
+ return "Pattern not found";
+ case SEARCH_NO_PATTERN:
+ return "No previous search pattern";
+ default:
+ return "This message should not be shown. "
+ "Please bug lumidify about it.";
}
}
diff --git a/search.h b/search.h
@@ -1,13 +1,25 @@
-enum ledit_search_state {
+#ifndef _SEARCH_H_
+#define _SEARCH_H_
+
+#include <stddef.h>
+
+typedef enum {
SEARCH_NORMAL,
SEARCH_WRAPPED,
SEARCH_NOT_FOUND,
SEARCH_NO_PATTERN
-};
+} search_state;
void search_cleanup(void);
void set_search_forward(char *pattern);
void set_search_backward(char *pattern);
-enum ledit_search_state ledit_search_next(ledit_view *view, size_t *line_ret, size_t *byte_ret);
-enum ledit_search_state ledit_search_prev(ledit_view *view, size_t *line_ret, size_t *byte_ret);
-char *search_state_to_str(enum ledit_search_state state);
+search_state ledit_search_next(ledit_view *view, size_t *line_ret, size_t *byte_ret);
+search_state ledit_search_prev(ledit_view *view, size_t *line_ret, size_t *byte_ret);
+
+/*
+ * Get a string corresponding to a search_state.
+ * This string should not be freed.
+ */
+char *search_state_to_str(search_state s);
+
+#endif
diff --git a/theme.h b/theme.h
@@ -1,3 +1,9 @@
+#ifndef _THEME_H_
+#define _THEME_H_
+
+#include <X11/Xft/Xft.h>
+#include "common.h"
+
typedef struct {
int scrollbar_width;
int scrollbar_step;
@@ -14,3 +20,5 @@ typedef struct {
ledit_theme *theme_create(ledit_common *common);
void theme_destroy(ledit_common *common, ledit_theme *theme);
+
+#endif
diff --git a/txtbuf.c b/txtbuf.c
@@ -1,6 +1,7 @@
#include <stdlib.h>
#include <string.h>
+#include "util.h"
#include "memory.h"
#include "txtbuf.h"
@@ -13,20 +14,13 @@ txtbuf_new(void) {
}
void
-txtbuf_grow(txtbuf *buf, size_t sz) {
+txtbuf_resize(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);
+ /* FIXME: '\0' isn't actually used anywhere */
+ size_t cap = ideal_array_size(buf->cap, add_sz(sz, 1));
+ if (cap != buf->cap) {
+ buf->text = ledit_realloc(buf->text, cap);
+ buf->cap = cap;
}
}
@@ -40,7 +34,7 @@ txtbuf_destroy(txtbuf *buf) {
void
txtbuf_copy(txtbuf *dst, txtbuf *src) {
- txtbuf_grow(dst, src->len);
+ txtbuf_resize(dst, src->len);
memcpy(dst->text, src->text, src->len);
dst->len = src->len;
}
diff --git a/txtbuf.h b/txtbuf.h
@@ -1,3 +1,8 @@
+#ifndef _TXTBUF_H_
+#define _TXTBUF_H_
+
+#include <stddef.h>
+
/*
* txtbuf is really just a string data type that is badly named.
*/
@@ -16,13 +21,7 @@ txtbuf *txtbuf_new(void);
* Make sure the txtbuf has space for at least the given size,
* plus '\0' at the end.
*/
-void txtbuf_grow(txtbuf *buf, size_t sz);
-
-/*
- * Shrink a textbuf, if the allocated space is much larger than the text.
- */
-/* FIXME: actually use this */
-void txtbuf_shrink(txtbuf *buf);
+void txtbuf_resize(txtbuf *buf, size_t sz);
/*
* Destroy a txtbuf.
@@ -38,3 +37,5 @@ void txtbuf_copy(txtbuf *dst, txtbuf *src);
* Duplicate txtbuf 'src'.
*/
txtbuf *txtbuf_dup(txtbuf *src);
+
+#endif
diff --git a/undo.c b/undo.c
@@ -6,6 +6,7 @@
#include <X11/Xutil.h>
#include <pango/pangoxft.h>
+#include "util.h"
#include "memory.h"
#include "common.h"
#include "txtbuf.h"
@@ -33,7 +34,7 @@ enum operation {
typedef struct {
txtbuf *text;
enum operation type;
- enum ledit_mode mode;
+ ledit_mode mode;
ledit_range op_range;
ledit_range cursor_range;
int group;
@@ -41,9 +42,9 @@ typedef struct {
} undo_elem;
struct undo_stack {
- /* FIXME: size_t? */
- int len, cur, cap;
+ size_t len, cur, cap;
undo_elem *stack;
+ int cur_valid;
int change_mode_group;
};
@@ -51,7 +52,8 @@ undo_stack *
undo_stack_create(void) {
undo_stack *undo = ledit_malloc(sizeof(undo_stack));
undo->len = undo->cap = 0;
- undo->cur = -1;
+ undo->cur = 0;
+ undo->cur_valid = 0;
undo->stack = NULL;
undo->change_mode_group = 0;
return undo;
@@ -59,6 +61,10 @@ undo_stack_create(void) {
void
undo_stack_destroy(undo_stack *undo) {
+ for (size_t i = 0; i < undo->cap; i++) {
+ if (undo->stack[i].text != NULL)
+ txtbuf_destroy(undo->stack[i].text);
+ }
free(undo->stack);
free(undo);
}
@@ -66,12 +72,14 @@ undo_stack_destroy(undo_stack *undo) {
/* FIXME: resize text buffers when they aren't needed anymore */
static undo_elem *
push_undo_elem(undo_stack *undo) {
- ledit_assert(undo->cur >= -1);
- undo->cur++;
- undo->len = undo->cur + 1;
+ if (undo->cur_valid)
+ undo->cur++;
+ else
+ undo->cur = 0;
+ undo->cur_valid = 1;
+ undo->len = add_sz(undo->cur, 1);
if (undo->len > undo->cap) {
- /* FIXME: wait, why is it size_t here already? */
- size_t cap = undo->len * 2;
+ size_t cap = ideal_array_size(undo->cap, undo->len);
undo->stack = ledit_reallocarray(undo->stack, cap, sizeof(undo_elem));
for (size_t i = undo->cap; i < cap; i++) {
undo->stack[i].text = NULL;
@@ -83,7 +91,7 @@ push_undo_elem(undo_stack *undo) {
static undo_elem *
peek_undo_elem(undo_stack *undo) {
- if (undo->cur < 0)
+ if (!undo->cur_valid)
return NULL;
return &undo->stack[undo->cur];
}
@@ -99,7 +107,7 @@ push_undo(
ledit_range insert_range, /* maybe not the best name */
ledit_range cursor_range,
int start_group,
- enum operation type, enum ledit_mode mode) {
+ enum operation type, 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;
@@ -121,7 +129,7 @@ void
undo_push_insert(
undo_stack *undo, txtbuf *text,
ledit_range insert_range, ledit_range cursor_range,
- int start_group, enum ledit_mode mode) {
+ int start_group, ledit_mode mode) {
push_undo(
undo, text, insert_range, cursor_range,
start_group, UNDO_INSERT, mode
@@ -132,7 +140,7 @@ void
undo_push_delete(
undo_stack *undo, txtbuf *text,
ledit_range delete_range, ledit_range cursor_range,
- int start_group, enum ledit_mode mode) {
+ int start_group, ledit_mode mode) {
push_undo(
undo, text, delete_range, cursor_range,
start_group, UNDO_DELETE, mode
@@ -140,15 +148,18 @@ undo_push_delete(
}
undo_status
-ledit_undo(undo_stack *undo, enum ledit_mode mode, void *callback_data,
+ledit_undo(undo_stack *undo, ledit_mode mode, void *callback_data,
undo_insert_callback insert_cb, undo_delete_callback delete_cb,
size_t *cur_line_ret, size_t *cur_index_ret, size_t *min_line_ret) {
undo_elem *e;
/* skip empty elements */
- while (undo->cur >= 0 && undo->stack[undo->cur].text->len == 0) {
- undo->cur--;
+ while (undo->cur_valid && undo->stack[undo->cur].text->len == 0) {
+ if (undo->cur == 0)
+ undo->cur_valid = 0;
+ else
+ undo->cur--;
}
- if (undo->cur < 0)
+ if (!undo->cur_valid)
return UNDO_OLDEST_CHANGE;
int group = undo->stack[undo->cur].group;
int mode_group = undo->stack[undo->cur].mode_group;
@@ -156,7 +167,7 @@ ledit_undo(undo_stack *undo, enum ledit_mode mode, void *callback_data,
int mode_group_same = 0;
size_t cur_line = 0;
size_t cur_index = 0;
- while (undo->cur >= 0 &&
+ while (undo->cur_valid &&
(undo->stack[undo->cur].group == group || (mode_group_same =
((mode == NORMAL ||
mode == VISUAL) &&
@@ -193,7 +204,10 @@ ledit_undo(undo_stack *undo, enum ledit_mode mode, void *callback_data,
/* FIXME: make sure this is always sorted already */
if (e->op_range.line1 < min_line)
min_line = e->op_range.line1;
- undo->cur--;
+ if (undo->cur == 0)
+ undo->cur_valid = 0;
+ else
+ undo->cur--;
cur_line = e->cursor_range.line1;
cur_index = e->cursor_range.byte1;
}
@@ -210,17 +224,24 @@ ledit_undo(undo_stack *undo, enum ledit_mode mode, void *callback_data,
}
undo_status
-ledit_redo(undo_stack *undo, enum ledit_mode mode, void *callback_data,
+ledit_redo(undo_stack *undo, ledit_mode mode, void *callback_data,
undo_insert_callback insert_cb, undo_delete_callback delete_cb,
size_t *cur_line_ret, size_t *cur_index_ret, size_t *min_line_ret) {
undo_elem *e;
+ if (undo->len == 0)
+ return UNDO_NEWEST_CHANGE;
/* skip elements where no text is changed */
while (undo->cur < undo->len - 1 && undo->stack[undo->cur + 1].text->len == 0) {
undo->cur++;
}
- if (undo->cur >= undo->len - 1)
+ if (undo->cur_valid && undo->cur >= undo->len - 1)
return UNDO_NEWEST_CHANGE;
- undo->cur++;
+ if (!undo->cur_valid) {
+ undo->cur_valid = 1;
+ undo->cur = 0;
+ } else {
+ undo->cur++;
+ }
int group = undo->stack[undo->cur].group;
int mode_group = undo->stack[undo->cur].mode_group;
size_t min_line = SIZE_MAX;
@@ -263,7 +284,9 @@ ledit_redo(undo_stack *undo, enum ledit_mode mode, void *callback_data,
}
*cur_line_ret = cur_line;
*cur_index_ret = cur_index;
- undo->cur--;
+ /* it should theoretically never be 0 anyways, but whatever */
+ if (undo->cur > 0)
+ undo->cur--;
*min_line_ret = min_line;
return UNDO_NORMAL;
}
@@ -271,5 +294,20 @@ ledit_redo(undo_stack *undo, enum ledit_mode mode, void *callback_data,
void
undo_change_last_cur_range(undo_stack *undo, ledit_range cur_range) {
undo_elem *e = peek_undo_elem(undo);
- e->cursor_range = cur_range;
+ if (e != NULL)
+ e->cursor_range = cur_range;
+}
+
+char *
+undo_state_to_str(undo_status s) {
+ switch (s) {
+ case UNDO_NORMAL:
+ return "Performed undo/redo";
+ case UNDO_OLDEST_CHANGE:
+ return "Already at oldest change";
+ case UNDO_NEWEST_CHANGE:
+ return "Already at newest change";
+ default:
+ return "This is a bug. Tell lumidify about it.";
+ }
}
diff --git a/undo.h b/undo.h
@@ -1,3 +1,10 @@
+#ifndef _UNDO_H_
+#define _UNDO_H_
+
+#include <stddef.h>
+#include "common.h"
+#include "txtbuf.h"
+
/*
* This handles undo and redo.
*
@@ -70,7 +77,7 @@ void undo_change_mode_group(undo_stack *undo);
void undo_push_insert(
undo_stack *undo, txtbuf *text,
ledit_range insert_range, ledit_range cursor_range,
- int start_group, enum ledit_mode mode
+ int start_group, ledit_mode mode
);
/*
@@ -80,7 +87,7 @@ void undo_push_insert(
void undo_push_delete(
undo_stack *undo, txtbuf *text,
ledit_range delete_range, ledit_range cursor_range,
- int start_group, enum ledit_mode mode
+ int start_group, ledit_mode mode
);
/*
@@ -88,9 +95,10 @@ void undo_push_delete(
* 'cur_line_ret' and 'cur_index_ret' are set to the new cursor position.
* 'min_line_ret' is set to the minimum line that was touched so the lines
* can be recalculated properly.
+ * WARNING: If nothing was changed, 'min_line_ret' will be SIZE_MAX.
*/
undo_status ledit_undo(
- undo_stack *undo, enum ledit_mode mode,
+ undo_stack *undo, ledit_mode mode,
void *callback_data, undo_insert_callback insert_cb, undo_delete_callback delete_cb,
size_t *cur_line_ret, size_t *cur_index_ret, size_t *min_line_ret
);
@@ -100,9 +108,10 @@ undo_status ledit_undo(
* 'cur_line_ret' and 'cur_index_ret' are set to the new cursor position.
* 'min_line_ret' is set to the minimum line that was touched so the lines
* can be recalculated properly.
+ * WARNING: If nothing was changed, 'min_line_ret' will be SIZE_MAX.
*/
undo_status ledit_redo(
- undo_stack *undo, enum ledit_mode mode,
+ undo_stack *undo, ledit_mode mode,
void *callback_data, undo_insert_callback insert_cb, undo_delete_callback delete_cb,
size_t *cur_line_ret, size_t *cur_index_ret, size_t *min_line_ret
);
@@ -115,3 +124,11 @@ undo_status ledit_redo(
* Fails silently if the stack is empty.
*/
void undo_change_last_cur_range(undo_stack *undo, ledit_range cur_range);
+
+/*
+ * Get a string corresponding to an undo_status.
+ * This string should not be freed.
+ */
+char *undo_state_to_str(undo_status s);
+
+#endif
diff --git a/util.c b/util.c
@@ -1,57 +1,40 @@
-#include <stdlib.h>
-
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <pango/pangoxft.h>
-#include <X11/extensions/Xdbe.h>
-
+#include <stddef.h>
#include "memory.h"
-#include "common.h"
-#include "txtbuf.h"
-#include "theme.h"
-#include "window.h"
-#include "util.h"
-ledit_draw *
-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(
- window->common->dpy, window->drawable, w, h, window->common->depth
- );
- draw->xftdraw = XftDrawCreate(
- window->common->dpy, draw->pixmap, window->common->vis, window->common->cm
- );
- return draw;
+char *
+next_utf8(char *str) {
+ while ((*str & 0xC0) == 0x80)
+ str++;
+ return str;
}
-void
-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(window->common->dpy, draw->pixmap);
- draw->pixmap = XCreatePixmap(
- window->common->dpy, window->drawable,
- draw->w, draw->h, window->common->depth
- );
- XftDrawChange(draw->xftdraw, draw->pixmap);
- }
+size_t
+add_sz(size_t a, size_t b) {
+ if (a > SIZE_MAX - b)
+ err_overflow();
+ return a + b;
+}
+
+size_t
+add_sz3(size_t a, size_t b, size_t c) {
+ if (b > SIZE_MAX - c || a > SIZE_MAX - (b + c))
+ err_overflow();
+ return a + b + c;
}
void
-draw_destroy(ledit_window *window, ledit_draw *draw) {
- XFreePixmap(window->common->dpy, draw->pixmap);
- XftDrawDestroy(draw->xftdraw);
- free(draw);
+swap_sz(size_t *a, size_t *b) {
+ size_t tmp = *a;
+ *a = *b;
+ *b = tmp;
}
-char *
-next_utf8(char *str) {
- while ((*str & 0xC0) == 0x80)
- str++;
- return str;
+void
+sort_range(size_t *l1, size_t *b1, size_t *l2, size_t *b2) {
+ if (*l1 == *l2 && *b1 > *b2) {
+ swap_sz(b1, b2);
+ } else if (*l1 > *l2) {
+ swap_sz(l1, l2);
+ swap_sz(b1, b2);
+ }
}
diff --git a/util.h b/util.h
@@ -1,28 +1,22 @@
-/* FIXME: rename this to draw_util.h and rename macros.h to util.h */
+#ifndef _UTIL_H_
+#define _UTIL_H_
/*
- * This is just a basic wrapper for XftDraws and Pixmaps
- * that is used by the window for its text display at the bottom.
+ * Return the position of the next start of a utf8 character.
+ * If there is none, the position of the terminating NUL is
+ * returned.
*/
-typedef struct {
- XftDraw *xftdraw;
- Pixmap pixmap;
- int w, h;
-} ledit_draw;
+char *next_utf8(char *str);
/*
- * Create a draw with the specified width and height.
+ * Add size_t values and abort if overflow would occur.
+ * FIXME: Maybe someone with actual experience could tell me
+ * if this overflow checking actually works.
*/
-ledit_draw *draw_create(ledit_window *window, int w, int h);
+size_t add_sz(size_t a, size_t b);
+size_t add_sz3(size_t a, size_t b, size_t c);
-/*
- * Make sure the size of the draw is at least the given width and height.
- */
-void draw_grow(ledit_window *window, ledit_draw *draw, int w, int h);
+void swap_sz(size_t *a, size_t *b);
+void sort_range(size_t *l1, size_t *b1, size_t *l2, size_t *b2);
-/*
- * Destroy a draw.
- */
-void draw_destroy(ledit_window *window, ledit_draw *draw);
-
-char *next_utf8(char *str);
+#endif
diff --git a/view.c b/view.c
@@ -1,6 +1,3 @@
-/* FIXME: shrink buffers when text length less than a fourth of the size */
-/* FIXME: handle all undo within buffer to keep it consistent */
-
#include <stdio.h>
#include <errno.h>
#include <string.h>
@@ -13,6 +10,7 @@
#include <pango/pangoxft.h>
#include <X11/extensions/Xdbe.h>
+#include "util.h"
#include "pango-compat.h"
#include "memory.h"
#include "common.h"
@@ -75,9 +73,6 @@ static PangoAttrList *get_pango_attributes(ledit_view *view, size_t start_byte,
*/
static void set_line_layout_attrs(ledit_view *view, size_t line, PangoLayout *layout);
-/* Swap size_t values. */
-static void swap_sz(size_t *a, size_t *b);
-
/* Move the gap of the line gap buffer to index 'index'. */
static void move_line_gap(ledit_view *view, size_t index);
@@ -90,14 +85,14 @@ static void resize_and_move_line_gap(ledit_view *view, size_t min_size, size_t i
/* FIXME: This is weird because mode is per-view but the undo mode group
is changed for the entire buffer. */
void
-view_set_mode(ledit_view *view, enum ledit_mode mode) {
+view_set_mode(ledit_view *view, ledit_mode mode) {
view->mode = mode;
undo_change_mode_group(view->buffer->undo);
window_set_mode(view->window, mode);
}
ledit_view *
-view_create(ledit_buffer *buffer, ledit_theme *theme, enum ledit_mode mode, size_t line, size_t pos) {
+view_create(ledit_buffer *buffer, ledit_theme *theme, ledit_mode mode, size_t line, size_t pos) {
if (basic_attrs == NULL) {
basic_attrs = pango_attr_list_new();
#if PANGO_VERSION_CHECK(1, 44, 0)
@@ -179,7 +174,6 @@ move_line_gap(ledit_view *view, size_t index) {
static void
resize_and_move_line_gap(ledit_view *view, size_t min_size, size_t index) {
- /* FIXME: Add to common bug list: used sizeof(ledit_line) instead of sizeof(ledit_view_line) */
view->lines = resize_and_move_gap(
view->lines, sizeof(ledit_view_line),
view->lines_gap, view->lines_cap, view->lines_num,
@@ -235,10 +229,7 @@ view_notify_delete_text(ledit_view *view, size_t line, size_t index, size_t len)
void
view_notify_append_line(ledit_view *view, size_t line) {
- size_t new_len = view->lines_num + 1;
- if (new_len <= view->lines_num)
- err_overflow();
- resize_and_move_line_gap(view, new_len, line + 1);
+ resize_and_move_line_gap(view, add_sz(view->lines_num, 1), line + 1);
if (line < view->cur_line)
view->cur_line++;
if (view->sel_valid)
@@ -278,6 +269,11 @@ view_notify_delete_lines(ledit_view *view, size_t index1, size_t index2) {
);
move_line_gap(view, index1);
view->lines_num -= index2 - index1 + 1;
+ /* possibly decrease size of array - this needs to be after
+ actually deleting the lines so the length is already less */
+ size_t min_size = ideal_array_size(view->lines_cap, view->lines_num);
+ if (min_size != view->lines_cap)
+ resize_and_move_line_gap(view, view->lines_num, view->lines_gap);
/* force first entry to offset 0 if first line was deleted */
if (index1 == 0) {
ledit_view_line *vl = view_get_line(view, 0);
@@ -329,7 +325,7 @@ set_line_layout_attrs(ledit_view *view, size_t line, PangoLayout *layout) {
PangoAttrList *list = NULL;
if (view->sel_valid) {
ledit_range sel = view->sel;
- view_sort_selection(&sel.line1, &sel.byte1, &sel.line2, &sel.byte2);
+ sort_range(&sel.line1, &sel.byte1, &sel.line2, &sel.byte2);
if (sel.line1 < line && sel.line2 > line) {
list = get_pango_attributes(view, 0, ll->len);
} else if (sel.line1 == line && sel.line2 == line) {
@@ -486,6 +482,7 @@ view_recalc_line(ledit_view *view, size_t line) {
* and adjust offsets of all lines following it */
if (l->h_dirty) {
l->h_dirty = 0;
+ /* FIXME: maybe also check overflow for offset? */
long off = l->y_offset + l->h;
for (size_t i = line + 1; i < view->lines_num; i++) {
l = view_get_line(view, i);
@@ -554,10 +551,8 @@ view_next_cursor_pos(ledit_view *view, size_t line, size_t byte, int num) {
PangoLayout *layout = get_pango_layout(view, line);
const PangoLogAttr *attrs =
pango_layout_get_log_attrs_readonly(layout, &nattrs);
- if (num < 0)
- return 0; /* FIXME: error */
for (size_t i = 0; i < (size_t)num; i++) {
- cur_byte = line_next_utf8(ll, byte);
+ cur_byte = line_next_utf8(ll, cur_byte);
for (c++; c < (size_t)nattrs; c++) {
if (attrs[c].is_cursor_position)
break;
@@ -578,8 +573,6 @@ view_prev_cursor_pos(ledit_view *view, size_t line, size_t byte, int num) {
PangoLayout *layout = get_pango_layout(view, line);
const PangoLogAttr *attrs =
pango_layout_get_log_attrs_readonly(layout, &nattrs);
- if (num < 0)
- return 0; /* FIXME: error */
for (int i = 0; i < num; i++) {
cur_byte = line_prev_utf8(ll, cur_byte);
for (; c > 0; c--) {
@@ -1069,7 +1062,6 @@ get_pango_layout(ledit_view *view, size_t line) {
return cl->layout;
}
-/* FIXME: document what works with pango units and what not */
void
view_pos_to_x_softline(ledit_view *view, size_t line, size_t pos, int *x_ret, int *softline_ret) {
ledit_view_line *vl = view_get_line(view, line);
@@ -1114,9 +1106,9 @@ view_x_softline_to_pos(ledit_view *view, size_t line, int x, int softline) {
pango_line, x_relative, &tmp_pos, &trailing
);
size_t pos = (size_t)tmp_pos;
- /* if in insert mode, snap to the nearest border between graphemes */
+ /* if in insert or visual mode, snap to the nearest border between graphemes */
/* FIXME: add parameter for this instead of checking mode */
- if (view->mode == INSERT) {
+ if (view->mode == INSERT || view->mode == VISUAL) {
ledit_line *ll = buffer_get_line(view->buffer, line);
while (trailing > 0) {
trailing--;
@@ -1176,9 +1168,6 @@ view_delete_range(
/* 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
view_delete_range_base(
ledit_view *view,
@@ -1194,7 +1183,7 @@ view_delete_range_base(
-> FIXME: why not just use view->cur_line, view->cur_index here? */
size_t cur_line = line_index1;
size_t cur_byte = byte_index1;
- view_sort_selection(&line_index1, &byte_index1, &line_index2, &byte_index2);
+ sort_range(&line_index1, &byte_index1, &line_index2, &byte_index2);
size_t new_line = 0, new_byte = 0;
ledit_assert(line_index1 < view->lines_num);
ledit_assert(line_index2 < view->lines_num);
@@ -1682,24 +1671,6 @@ view_ensure_cursor_shown(ledit_view *view) {
}
}
-static void
-swap_sz(size_t *a, size_t *b) {
- size_t tmp = *a;
- *a = *b;
- *b = tmp;
-}
-
-/* FIXME: this is generic, so it doesn't need to be in view.c */
-void
-view_sort_selection(size_t *line1, size_t *byte1, size_t *line2, size_t *byte2) {
- if (*line1 > *line2) {
- swap_sz(line1, line2);
- swap_sz(byte1, byte2);
- } else if (*line1 == *line2 && *byte1 > *byte2) {
- swap_sz(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! */
@@ -1740,8 +1711,8 @@ view_set_selection(ledit_view *view, size_t line1, size_t byte1, size_t line2, s
}
size_t l1_new = line1, l2_new = line2;
size_t b1_new = byte1, b2_new = byte2;
- view_sort_selection(&l1_new, &b1_new, &l2_new, &b2_new);
- view_sort_selection(&view->sel.line1, &view->sel.byte1, &view->sel.line2, &view->sel.byte2);
+ sort_range(&l1_new, &b1_new, &l2_new, &b2_new);
+ sort_range(&view->sel.line1, &view->sel.byte1, &view->sel.line2, &view->sel.byte2);
/* FIXME: make this a bit nicer and optimize it */
if (view->sel.line1 > l2_new || view->sel.line2 < l1_new) {
for (size_t i = view->sel.line1; i <= view->sel.line2; i++) {
@@ -1772,6 +1743,7 @@ view_set_selection(ledit_view *view, size_t line1, size_t byte1, size_t line2, s
view->sel.line2 = line2;
view->sel.byte2 = byte2;
view->sel_valid = 1;
+ view->redraw = 1;
}
static void
@@ -1815,8 +1787,8 @@ view_button_handler(void *data, XEvent *event) {
view_wipe_line_cursor_attrs(view, view->cur_line);
/* FIXME: return to old mode afterwards? */
/* should change_mode_group even be called here? */
- view_set_mode(view, VISUAL);
}
+ view_set_mode(view, VISUAL);
if (!view->sel_valid) {
/* the selection has just started, so the current
position is already set to the beginning of the
@@ -1962,32 +1934,43 @@ view_redraw(ledit_view *view) {
}
void
-view_undo(ledit_view *view) {
- /* FIXME: maybe wipe selection */
- size_t old_line = view->cur_line;
- buffer_undo(view->buffer, view->mode, &view->cur_line, &view->cur_index);
- if (view->mode == NORMAL) {
- view->cur_index = view_get_legal_normal_pos(
- view, view->cur_line, view->cur_index
- );
+view_undo(ledit_view *view, int num) {
+ /* FIXME: maybe wipe selection (although I guess this
+ currently isn't possible in visual mode anyways) */
+ for (int i = 0; i < num; i++) {
+ size_t old_line = view->cur_line;
+ undo_status s = buffer_undo(view->buffer, view->mode, &view->cur_line, &view->cur_index);
+ if (view->mode == NORMAL) {
+ view->cur_index = view_get_legal_normal_pos(
+ view, view->cur_line, view->cur_index
+ );
+ }
+ view_wipe_line_cursor_attrs(view, old_line);
+ view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
+ if (s != UNDO_NORMAL) {
+ window_show_message(view->window, undo_state_to_str(s), -1);
+ break;
+ }
}
- view_wipe_line_cursor_attrs(view, old_line);
- view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
- /* FIXME: show undo message */
}
void
-view_redo(ledit_view *view) {
- size_t old_line = view->cur_line;
- buffer_redo(view->buffer, view->mode, &view->cur_line, &view->cur_index);
- if (view->mode == NORMAL) {
- view->cur_index = view_get_legal_normal_pos(
- view, view->cur_line, view->cur_index
- );
+view_redo(ledit_view *view, int num) {
+ for (int i = 0; i < num; i++) {
+ size_t old_line = view->cur_line;
+ undo_status s = buffer_redo(view->buffer, view->mode, &view->cur_line, &view->cur_index);
+ if (view->mode == NORMAL) {
+ view->cur_index = view_get_legal_normal_pos(
+ view, view->cur_line, view->cur_index
+ );
+ }
+ view_wipe_line_cursor_attrs(view, old_line);
+ view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
+ if (s != UNDO_NORMAL) {
+ window_show_message(view->window, undo_state_to_str(s), -1);
+ break;
+ }
}
- view_wipe_line_cursor_attrs(view, old_line);
- view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
- /* FIXME: show undo message */
}
static void
diff --git a/view.h b/view.h
@@ -6,6 +6,13 @@
#ifndef _LEDIT_VIEW_H_
#define _LEDIT_VIEW_H_
+#include <stddef.h>
+#include "common.h"
+#include "txtbuf.h"
+#include "window.h"
+#include "theme.h"
+#include "cache.h"
+
typedef struct ledit_view ledit_view;
#include "buffer.h"
@@ -69,7 +76,7 @@ struct ledit_view {
long total_height; /* total pixel height of all lines */
long display_offset; /* current pixel offset of viewport */
ledit_range sel; /* current selection */
- enum ledit_mode mode; /* current mode of this view */
+ ledit_mode mode; /* current mode of this view */
char selecting; /* whether user is currently selecting text with mouse */
char sel_valid; /* whether there is currently a valid selection */
char redraw; /* whether something has changed so the view needs to be redrawn */
@@ -86,7 +93,7 @@ enum delete_mode {
* This changes the mode group of the associated buffer's
* undo stack and changes the mode display in the window.
*/
-void view_set_mode(ledit_view *view, enum ledit_mode mode);
+void view_set_mode(ledit_view *view, ledit_mode mode);
/*
* Create a view with associated buffer 'buffer' and theme 'theme'.
@@ -95,7 +102,7 @@ void view_set_mode(ledit_view *view, enum ledit_mode mode);
*/
ledit_view *view_create(
ledit_buffer *buffer, ledit_theme *theme,
- enum ledit_mode mode, size_t line, size_t pos
+ ledit_mode mode, size_t line, size_t pos
);
/*
@@ -442,11 +449,6 @@ void view_scroll_to_pos_bottom(ledit_view *view, size_t line, size_t byte);
void view_ensure_cursor_shown(ledit_view *view);
/*
- * Sort the given range so that (*line1 < *line2) or (*line1 == *line2 && *byte1 <= *byte2).
- */
-void view_sort_selection(size_t *line1, size_t *byte1, size_t *line2, size_t *byte2);
-
-/*
* Clear the selection.
*/
void view_wipe_selection(ledit_view *view);
@@ -465,20 +467,20 @@ void view_set_selection(ledit_view *view, size_t line1, size_t byte1, size_t lin
void view_redraw(ledit_view *view);
/*
- * Perform an undo step.
+ * Perform up to num undo steps.
* The cursor position of the view is set to the stored position
* in the undo stack.
* The line heights and offsets are recalculated.
*/
-void view_undo(ledit_view *view);
+void view_undo(ledit_view *view, int num);
/*
- * Perform a redo step.
+ * Perform up to num redo steps.
* The cursor position of the view is set to the stored position
* in the undo stack.
* The line heights and offsets are recalculated.
*/
-void view_redo(ledit_view *view);
+void view_redo(ledit_view *view, int num);
/*
* Paste the X11 clipboard at the current cursor position.
diff --git a/window.c b/window.c
@@ -9,19 +9,18 @@
#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 "util.h"
+#include "theme.h"
#include "memory.h"
#include "common.h"
#include "txtbuf.h"
-#include "theme.h"
#include "window.h"
-#include "util.h"
#include "macros.h"
#include "config.h"
#include "assert.h"
+#include "draw_util.h"
/* FIXME: Everything to do with the bottom bar is extremely hacky */
struct bottom_bar {
@@ -106,14 +105,14 @@ recalc_text_size(ledit_window *window) {
static void
resize_line_text(ledit_window *window, int min_size) {
- if (min_size > window->bb->line_alloc || window->bb->line_text == NULL) {
- /* FIXME: read up on what the best values are here */
- /* FIXME: overflow */
- window->bb->line_alloc =
- window->bb->line_alloc * 2 > min_size ?
- window->bb->line_alloc * 2 :
- min_size;
- window->bb->line_text = ledit_realloc(window->bb->line_text, window->bb->line_alloc);
+ /* FIXME: use size_t everywhere */
+ ledit_assert(min_size >= 0);
+ size_t cap = ideal_array_size(window->bb->line_alloc, min_size);
+ if (cap > INT_MAX)
+ err_overflow();
+ if (cap != (size_t)window->bb->line_alloc) {
+ window->bb->line_alloc = (int)cap;
+ window->bb->line_text = ledit_realloc(window->bb->line_text, cap);
}
}
@@ -337,7 +336,7 @@ window_hide_message(ledit_window *window) {
}
void
-window_set_mode(ledit_window *window, enum ledit_mode mode) {
+window_set_mode(ledit_window *window, ledit_mode mode) {
window->mode = mode;
char *text;
switch (mode) {
@@ -516,7 +515,7 @@ xximspot(ledit_window *window, int x, int y) {
}
ledit_window *
-window_create(ledit_common *common, ledit_theme *theme, enum ledit_mode mode) {
+window_create(ledit_common *common, ledit_theme *theme, ledit_mode mode) {
XSetWindowAttributes attrs;
XGCValues gcv;
@@ -659,7 +658,6 @@ window_destroy(ledit_window *window) {
/*g_object_unref(window->context);*/
g_object_unref(window->fontmap);
- /* FIXME: is gc, etc. destroyed automatically when destroying window? */
if (window->spotlist)
XFree(window->spotlist);
XDestroyWindow(window->common->dpy, window->xwin);
diff --git a/window.h b/window.h
@@ -1,3 +1,6 @@
+#ifndef _WINDOW_H_
+#define _WINDOW_H_
+
/*
* A window is associated with exactly one view and is responsible everything
* other than the actual text handling (of the text in the buffer).
@@ -5,10 +8,17 @@
* partially here and partially in keys_command, but that's the way it is for now.
*/
-#ifndef _WINDOW_H_
-#define _WINDOW_H_
-
+#include <time.h>
#include <stdarg.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/Xdbe.h>
+#include <pango/pangoxft.h>
+
+#include "theme.h"
+#include "common.h"
+#include "txtbuf.h"
typedef struct bottom_bar bottom_bar;
@@ -42,7 +52,7 @@ typedef struct {
int message_shown; /* whether a readonly message is shown at the bottom */
bottom_bar *bb; /* encapsulates the text at the bottom */
int redraw; /* whether something has changed and the window has to be redrawn */
- enum ledit_mode mode; /* mode of the view - a bit ugly to duplicate this here... */
+ ledit_mode mode; /* mode of the view - a bit ugly to duplicate this here... */
/* stuff for filtering events so not too many have to be handled */
struct timespec last_scroll;
@@ -83,7 +93,7 @@ typedef struct {
/*
* Create a window with initial mode 'mode'.
*/
-ledit_window *window_create(ledit_common *common, ledit_theme *theme, enum ledit_mode mode);
+ledit_window *window_create(ledit_common *common, ledit_theme *theme, ledit_mode mode);
/*
* Destroy a window.
@@ -171,6 +181,8 @@ void window_set_bottom_bar_realtext(ledit_window *window, char *text, int len);
/*
* Get the text of the editable line.
+ * WARNING: this is a direct pointer to the internal storage,
+ * it is not copied.
*/
char *window_get_bottom_bar_text(ledit_window *window);
@@ -183,6 +195,10 @@ void window_show_message(ledit_window *window, char *text, int len);
* Show a non-editable message that is given as a
* format string and arguments as interpreted by
* vsnprintf(3)
+ * WARNING: This may reallocate the internal text storage for the
+ * bottom bar before actually using the format arguments, so don't
+ * ever pass parts of the text returned by window_get_bottom_bar_text
+ * to this function without first copying it.
*/
void window_show_message_fmt(ledit_window *window, char *fmt, ...);
@@ -199,7 +215,7 @@ int window_message_shown(ledit_window *window);
/*
* Set the displayed mode of the window.
*/
-void window_set_mode(ledit_window *window, enum ledit_mode mode);
+void window_set_mode(ledit_window *window, ledit_mode mode);
/*
* Set extra text that is shown to the right of the mode.