tStart cleaning up and adding documentation - ledit - Text editor (WIP) | |
git clone git://lumidify.org/ledit.git (fast, but not encrypted) | |
git clone https://lumidify.org/git/ledit.git (encrypted, but very slow) | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 7892e89a47ccb35549b25fe558245b9e8f639daf | |
parent 22ffb413e9e0be6a5764de4bcc951e6a0480bad3 | |
Author: lumidify <[email protected]> | |
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 | |
t@@ -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 | |
t@@ -24,6 +24,7 @@ OBJ = \ | |
txtbuf.o \ | |
undo.o \ | |
util.o \ | |
+ draw_util.o \ | |
window.o \ | |
pango-compat.o | |
t@@ -42,6 +43,7 @@ HDR = \ | |
txtbuf.h \ | |
undo.h \ | |
util.h \ | |
+ draw_util.h \ | |
window.h \ | |
cleanup.h \ | |
pango-compat.h | |
t@@ -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 | |
t@@ -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 | |
t@@ -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 | |
t@@ -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/… | |
+http://lumidify.org/doc/ledit/ledit-current.html | |
+http://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/doc/ledi… | |
+https://lumidify.org/doc/ledit/ledit-current.html | |
diff --git a/TODO b/TODO | |
t@@ -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 | |
t@@ -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 | |
t@@ -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 … | |
t@@ -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: | |
t@@ -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); | |
t@@ -150,27 +144,18 @@ static void buffer_delete_line_entries_base(ledit_buffer… | |
*/ | |
static void buffer_delete_line_section_base(ledit_buffer *buffer, size_t line,… | |
+/* | |
+ * Copy text range into given buffer. | |
+ * - dst is null-terminated | |
+ * - dst must be large enough to contain the text and NUL (only use this toget… | |
+ * - the range must be sorted already | |
+ */ | |
+static void buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int b… | |
+ | |
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); | |
t@@ -190,7 +175,7 @@ buffer_insert_mark(ledit_buffer *buffer, char *mark, size_… | |
} | |
} | |
if (marklist->len == marklist->alloc) { | |
- size_t new_alloc = marklist->alloc > 0 ? marklist->alloc * 2 :… | |
+ size_t new_alloc = ideal_array_size(marklist->alloc, add_sz(ma… | |
marklist->marks = ledit_reallocarray( | |
marklist->marks, new_alloc, sizeof(ledit_buffer_mark) | |
); | |
t@@ -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 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, siz… | |
+ size_t new_num = add_sz(buffer->views_num, 1); | |
buffer->views = ledit_reallocarray(buffer->views, new_num, sizeof(ledi… | |
buffer->views[buffer->views_num] = view_create(buffer, theme, mode, li… | |
set_view_hard_line_text(buffer, buffer->views[buffer->views_num]); | |
t@@ -354,10 +337,11 @@ buffer_load_file(ledit_buffer *buffer, char *filename, s… | |
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) { | |
t@@ -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) { | |
t@@ -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, | |
t@@ -593,12 +568,9 @@ resize_and_move_line_gap(ledit_buffer *buffer, size_t min… | |
static void | |
buffer_insert_text_base(ledit_buffer *buffer, size_t line_index, size_t index,… | |
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... i… | |
- 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 th… | |
memcpy(line->text + index, text, len); | |
line->gap += len; | |
t@@ -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 i… | |
buffer_insert_text_base(buffer, cur_line, cur_index, last, text + len … | |
if (end_line_ret) | |
*end_line_ret = cur_line; | |
t@@ -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_i… | |
- 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++; | |
t@@ -721,6 +688,11 @@ buffer_delete_line_entries_base(ledit_buffer *buffer, siz… | |
} | |
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_nu… | |
+ if (min_size != buffer->lines_cap) | |
+ resize_and_move_line_gap(buffer, buffer->lines_num, buffer->li… | |
for (size_t i = 0; i < buffer->views_num; i++) { | |
view_notify_delete_lines(buffer->views[i], index1, index2); | |
} | |
t@@ -760,14 +732,24 @@ buffer_textlen(ledit_buffer *buffer, size_t line1, size_… | |
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; | |
t@@ -779,7 +761,7 @@ buffer_textlen(ledit_buffer *buffer, size_t line1, size_t … | |
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 li… | |
ledit_assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); | |
ledit_line *ll1 = buffer_get_line(buffer, line1); | |
t@@ -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; | |
} | |
t@@ -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; | |
t@@ -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 star… | |
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) { | |
t@@ -882,6 +865,10 @@ buffer_delete_line_section_base(ledit_buffer *buffer, siz… | |
); | |
} | |
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); | |
} | |
t@@ -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 | |
); | |
t@@ -942,26 +931,26 @@ undo_delete_helper(void *data, size_t line1, size_t byte… | |
delete_range_base(buffer, line1, byte1, line2, byte2, NULL); | |
} | |
-void | |
-buffer_undo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size… | |
+undo_status | |
+buffer_undo(ledit_buffer *buffer, ledit_mode mode, size_t *cur_line, size_t *c… | |
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… | |
+undo_status | |
+buffer_redo(ledit_buffer *buffer, ledit_mode mode, size_t *cur_line, size_t *c… | |
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 | |
); | |
t@@ -971,12 +960,13 @@ buffer_redo(ledit_buffer *buffer, enum ledit_mode mode, … | |
); | |
} | |
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) { | |
t@@ -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) { | |
t@@ -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) { | |
t@@ -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 | |
t@@ -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" | |
t@@ -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 | |
); | |
/* | |
t@@ -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… | |
/* | |
- * Copy text range into given buffer. | |
- * - dst is null-terminated | |
- * - dst must be large enough to contain the text and NUL (only use this toget… | |
- * - the range must be sorted already | |
- */ | |
-void buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, i… | |
- | |
-/* | |
* Copy text range into given buffer and resize it if necessary. | |
* - the range must be sorted already | |
*/ | |
t@@ -219,12 +219,12 @@ int buffer_get_mark(ledit_buffer *buffer, char *mark, si… | |
* '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,… | |
+undo_status buffer_undo(ledit_buffer *buffer, ledit_mode mode, size_t *cur_lin… | |
/* | |
* Same as 'buffer_undo', but for redo. | |
*/ | |
-void buffer_redo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line,… | |
+undo_status buffer_redo(ledit_buffer *buffer, ledit_mode mode, size_t *cur_lin… | |
/* | |
* Delete the given range (which does not need to be sorted yet) and | |
t@@ -237,7 +237,7 @@ void buffer_redo(ledit_buffer *buffer, enum ledit_mode mod… | |
*/ | |
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 | |
t@@ -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 | |
t@@ -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 | |
t@@ -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 | |
t@@ -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" | |
t@@ -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( | |
t@@ -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… | |
+ size_t new_alloc = ideal_array_size(cache->num_pixmaps, add_sz(cache->… | |
+ cache->pixmaps = ledit_reallocarray(cache->pixmaps, new_alloc, sizeof(… | |
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 | |
t@@ -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. | |
*/ | |
t@@ -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 | |
t@@ -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 | |
t@@ -1,3 +1,8 @@ | |
+#ifndef _COMMON_H_ | |
+#define _COMMON_H_ | |
+ | |
+#include <X11/Xlib.h> | |
+ | |
typedef struct { | |
size_t line1; | |
size_t byte1; | |
t@@ -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; | |
t@@ -19,3 +24,5 @@ typedef struct { | |
int depth; | |
int redraw; | |
} ledit_common; | |
+ | |
+#endif | |
diff --git a/draw_util.c b/draw_util.c | |
t@@ -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->co… | |
+ ); | |
+ 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 | |
t@@ -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 | |
t@@ -13,6 +13,7 @@ | |
#include "theme.h" | |
#include "window.h" | |
#include "keys.h" | |
+#include "keys_config.h" | |
KEY_LANGS; | |
t@@ -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 | |
t@@ -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); | |
t@@ -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 | |
t@@ -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> | |
t@@ -20,6 +20,7 @@ | |
#include <X11/XF86keysym.h> | |
#include <X11/cursorfont.h> | |
+#include "util.h" | |
#include "memory.h" | |
#include "common.h" | |
#include "txtbuf.h" | |
t@@ -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" | |
t@@ -108,7 +110,7 @@ void clear_key_stack(void); | |
static void move_cursor_left_right(ledit_view *view, int dir, int allow_illega… | |
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… | |
static void yank_cb(ledit_view *view, size_t line, size_t char_pos, enum key_t… | |
static void get_new_line_softline( | |
t@@ -129,14 +131,6 @@ view_locked_error(ledit_view *view) { | |
#define CHECK_VIEW_LOCKED(view) if (view->lock_text) return view_locked_error(… | |
#define CHECK_VIEW_LOCKED_NORETURN(view) if (view->lock_text) (void)view_locke… | |
-/* 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; | |
t@@ -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 :… | |
+ size_t new_alloc = ideal_array_size(key_stack.alloc, add_sz(ke… | |
key_stack.stack = ledit_reallocarray( | |
key_stack.stack, new_alloc, sizeof(struct key_stack_elem) | |
); | |
t@@ -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) { | |
t@@ -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; | |
t@@ -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… | |
+ size_t new_alloc = ideal_array_size(repetition_stack.tmp_alloc… | |
repetition_stack.tmp_stack = ledit_reallocarray( | |
repetition_stack.tmp_stack, | |
new_alloc, sizeof(struct repetition_stack_elem) | |
t@@ -507,6 +504,7 @@ delete_chars_forwards(ledit_view *view, char *text, size_t… | |
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}; | |
} | |
t@@ -537,6 +535,7 @@ delete_chars_backwards(ledit_view *view, char *text, size_… | |
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}; | |
} | |
t@@ -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 : (s… | |
t@@ -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 d… | |
+ callback deletes until the end of the line even in soft lin… | |
+ 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; | |
t@@ -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 | |
t@@ -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) { | |
t@@ -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, &vie… | |
); | |
buffer_copy_text_to_txtbuf( | |
t@@ -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) { | |
t@@ -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)) { | |
t@@ -1255,20 +1259,27 @@ paste_normal_backwards(ledit_view *view, char *text, s… | |
} | |
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; | |
} | |
t@@ -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}; | |
} | |
t@@ -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}; | |
} | |
t@@ -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}; | |
} | |
t@@ -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}; | |
} | |
t@@ -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}; | |
} | |
t@@ -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}; | |
} | |
t@@ -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}; | |
} | |
t@@ -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}; | |
} | |
t@@ -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}; | |
} | |
t@@ -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}; | |
} | |
t@@ -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) | |
t@@ -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) … | |
t@@ -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) | |
t@@ -1540,6 +1551,8 @@ move_cursor_left_right(ledit_view *view, int dir, int al… | |
if (view->mode == VISUAL) { | |
view_set_selection(view, view->sel.line1, view->sel.by… | |
} 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-… | |
t@@ -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}; | |
t@@ -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) | |
t@@ -1649,8 +1665,6 @@ move_cursor_up_down(ledit_view *view, int dir) { | |
view_get_softline_bounds(view, new_line, new_softline, &start,… | |
cb(view, new_line, start, KEY_MOTION_LINE); | |
} else { | |
- /* FIXME: when selecting on last line, moving down moves the c… | |
- one (when it stays on the same line because it's the last o… | |
int lineno, x; | |
view_pos_to_x_softline(view, view->cur_line, view->cur_index, … | |
view->cur_index = view_x_softline_to_pos(view, new_line, x, ne… | |
t@@ -1744,7 +1758,7 @@ cursor_to_first_non_ws(ledit_view *view, char *text, siz… | |
(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; | |
t@@ -1778,7 +1792,7 @@ cursor_to_beginning(ledit_view *view, char *text, size_t… | |
(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? */ | |
t@@ -1831,13 +1845,11 @@ switch_selection_end(ledit_view *view, char *text, siz… | |
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)); | |
t@@ -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; | |
t@@ -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,… | |
- char *str = ledit_malloc(textlen + 1); | |
- snprintf(str, textlen + 1, "Line %zu of %zu", view->cur_line + 1, view… | |
- 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}; | |
t@@ -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}; | |
t@@ -2081,7 +2105,7 @@ dummy_cursor_helper(ledit_view *view, size_t line, size_… | |
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) … | |
t@@ -2249,15 +2273,26 @@ repeat_command(ledit_view *view, char *text, size_t le… | |
(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_sta… | |
- 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_ele… | |
+ while (e) { | |
+ (void)handle_key(view, e->key_text, e->len, e->sym, e-… | |
+ advance_repetition_stack(); | |
+ e = get_cur_repetition_stack_elem(); | |
+ } | |
} | |
repetition_stack.replaying = 0; | |
discard_repetition_stack(); | |
t@@ -2290,11 +2325,13 @@ basic_key_handler(ledit_view *view, XEvent *event, int… | |
struct action act = handle_key(view, buf, (size_t)n, sym, key_state, l… | |
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. curs… | |
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 | |
t@@ -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_inde… | |
+ | |
+#endif | |
diff --git a/keys_basic_config.h b/keys_basic_config.h | |
t@@ -20,7 +20,7 @@ struct key { | |
char *text; /* for keys that … | |
unsigned int mods; /* modifier mask … | |
KeySym keysym; /* for other keys… | |
- enum ledit_mode modes; /* modes in which… | |
+ ledit_mode modes; /* modes in which this… | |
enum key_type prev_keys; /* allowed previo… | |
enum key_type key_types; /* key types - us… | |
struct action (*func)(ledit_view *, char *, size_t); /* callback funct… | |
t@@ -109,7 +109,7 @@ static struct key keys_en[] = { | |
{NULL, 0, XK_Right, VISUAL|INSERT|NORMAL, KEY_ANY, KEY_ANY, &cursor_ri… | |
{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_dow… | |
- {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_k… | |
{"i", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_insert}, | |
t@@ -142,16 +142,16 @@ static struct key keys_en[] = { | |
{"c", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION|KEY_NUMBERALLOWED, &ch… | |
{"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_backwa… | |
- {"/", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &enter_searchedit_forwar… | |
- {"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: onl… | |
{"z", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &undo}, | |
{"y", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &redo}, /* FIXME: thi… | |
diff --git a/keys_command.c b/keys_command.c | |
t@@ -27,6 +27,7 @@ | |
#include "util.h" | |
#include "keys.h" | |
+#include "keys_config.h" | |
#include "keys_command.h" | |
#include "keys_command_config.h" | |
t@@ -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 : h… | |
- 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; | |
} | |
t@@ -100,15 +99,17 @@ view_locked_error(ledit_view *view) { | |
#define CHECK_VIEW_LOCKED(view) if (view->lock_text) return view_locked_error(… | |
-/* 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… | |
+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 */ | |
t@@ -217,12 +218,7 @@ handle_write_quit(ledit_view *view, char *cmd, size_t l1,… | |
static void | |
show_num_substituted(ledit_view *view) { | |
- char buf[30]; | |
- if (snprintf(buf, sizeof(buf), "%d substitution(s)", sub_state.num) < … | |
- window_show_message(view->window, "This message is a bug, tell… | |
- } else { | |
- window_show_message(view->window, buf, -1); | |
- } | |
+ window_show_message_fmt(view->window, "%d substitution(s)", sub_state.… | |
} | |
/* returns 1 when match was found, 0 otherwise */ | |
t@@ -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_s… | |
/* this doesn't need to be added to the undo stack since it's called o… | |
view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view… | |
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); | |
} | |
t@@ -369,6 +364,11 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1,… | |
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 subs… | |
view->cur_command_type = CMD_SUBSTITUTE; | |
t@@ -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 *l… | |
+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, | |
t@@ -426,14 +430,22 @@ parse_range(ledit_view *view, char *cmd, size_t len, cha… | |
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 ran… | |
+ return 1; | |
+ } | |
+ *final *= 10; | |
+ if (SIZE_MAX - (*c - '0') < *final) { | |
+ *errstr_ret = "Integer overflow in ran… | |
+ return 1; | |
+ } | |
+ *final += (*c - '0'); | |
} else if ((s & START_LINENO) && (s & START_RANGE)) { | |
l1 = *c - '0'; | |
*l1_valid = 1; | |
t@@ -444,8 +456,10 @@ parse_range(ledit_view *view, char *cmd, size_t len, char… | |
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; | |
t@@ -455,7 +469,7 @@ parse_range(ledit_view *view, char *cmd, size_t len, char … | |
l = view->sel.line1 > view->sel.line2 ? view->… | |
} else { | |
if (buffer_get_mark(view->buffer, c + 1, markl… | |
- /* FIXME: show better error message */ | |
+ *errstr_ret = "Invalid mark"; | |
return 1; | |
} | |
} | |
t@@ -471,6 +485,7 @@ parse_range(ledit_view *view, char *cmd, size_t len, char … | |
continue; | |
} else if (*c == ',' && !(s & START_RANGE)) { | |
if (*l1_valid && *l2_valid) { | |
+ *errstr_ret = "Invalid range"; | |
return 1; | |
} else { | |
s = START_LINENO; | |
t@@ -481,11 +496,13 @@ parse_range(ledit_view *view, char *cmd, size_t len, cha… | |
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; | |
t@@ -496,6 +513,7 @@ parse_range(ledit_view *view, char *cmd, size_t len, char … | |
} | |
s = 0; | |
} else { | |
+ *errstr_ret = "Invalid range"; | |
return 1; | |
} | |
} else if (*c == '$') { | |
t@@ -509,6 +527,7 @@ parse_range(ledit_view *view, char *cmd, size_t len, char … | |
} | |
s = 0; | |
} else { | |
+ *errstr_ret = "Invalid range"; | |
return 1; | |
} | |
} else { | |
t@@ -516,10 +535,14 @@ parse_range(ledit_view *view, char *cmd, size_t len, cha… | |
} | |
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->line… | |
- return 1; /* FIXME: better error messages */ | |
+ } | |
+ if ((*l1_valid || *l2_valid) && (l1 == 0 || l2 == 0 || l1 > view->line… | |
+ *errstr_ret = "Invalid line number in range"; | |
+ return 1; | |
+ } | |
*cmd_ret = c; | |
/* ranges are given 1-indexed by user */ | |
*line1_ret = l1 - 1; | |
t@@ -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, &e… | |
+ window_show_message(view->window, errstr, -1); | |
return 0; | |
} | |
int range_given = l1_valid && l2_valid; | |
t@@ -743,7 +767,7 @@ edit_nextsearch(ledit_view *view, char *key_text, size_t l… | |
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,… | |
+ search_state ret = ledit_search_next(view, &view->cur_line, &view->cur… | |
view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); | |
view_ensure_cursor_shown(view); | |
if (ret != SEARCH_NORMAL) | |
t@@ -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,… | |
+ search_state ret = ledit_search_prev(view, &view->cur_line, &view->cur… | |
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 | |
t@@ -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_in… | |
+ | |
+#endif | |
diff --git a/keys_command_config.h b/keys_command_config.h | |
t@@ -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 | |
t@@ -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 | |
t@@ -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 curren… | |
+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 val… | |
+.Cm % , | |
+which refers to the entire file. | |
+The following special values are possible instead of writing a line number dir… | |
+.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 … | |
+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 [email protected] | |
+.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 | |
t@@ -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 */ | |
t@@ -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 … | |
/* FIXME: horizontal scrolling (also need cache to avoid too large pixmaps) */ | |
-/* FIXME: sort out types for indices (currently just int, but that might overf… | |
/* 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[]); | |
t@@ -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 | |
t@@ -1,3 +1,6 @@ | |
+#ifndef _MACROS_H_ | |
+#define _MACROS_H_ | |
+ | |
/* stolen from OpenBSD */ | |
#define ledit_timespecsub(tsp, usp, vsp) … | |
do { \ | |
t@@ -8,3 +11,5 @@ | |
(vsp)->tv_nsec += 1000000000L; \ | |
} \ | |
} while (0) | |
+ | |
+#endif | |
diff --git a/memory.c b/memory.c | |
t@@ -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) { | |
t@@ -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; | |
t@@ -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); | |
} | |
t@@ -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 ar… | |
- is of that size, so all the other multiplications here should be sa… | |
- (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 ari… | |
+ /* we already know new_cap * elem_size does not wrap around be… | |
+ is of that size, so all the other multiplications here shou… | |
+ (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 | |
t@@ -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. | |
*/ | |
t@@ -28,6 +34,7 @@ void move_gap( | |
/* | |
* Resize a generic gap buffer with elements of size 'elem_size' to hold at le… | |
* '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… | |
* array (number of elements, not bytes), and 'len' is the number of valid ele… | |
* 'index' is also written to 'new_gap_ret' if it is not NULL. This is just | |
t@@ -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 | |
t@@ -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 | |
t@@ -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; | |
t@@ -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; | |
t@@ -93,7 +80,7 @@ search_forward(ledit_view *view, size_t *line_ret, size_t *b… | |
/* 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; | |
t@@ -157,7 +144,7 @@ search_backward(ledit_view *view, size_t *line_ret, size_t… | |
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); | |
t@@ -165,7 +152,7 @@ ledit_search_next(ledit_view *view, size_t *line_ret, size… | |
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); | |
t@@ -174,16 +161,18 @@ ledit_search_prev(ledit_view *view, size_t *line_ret, si… | |
} | |
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 | |
t@@ -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, … | |
-enum ledit_search_state ledit_search_prev(ledit_view *view, size_t *line_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 *byt… | |
+search_state ledit_search_prev(ledit_view *view, size_t *line_ret, size_t *byt… | |
+ | |
+/* | |
+ * 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 | |
t@@ -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; | |
t@@ -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 | |
t@@ -1,6 +1,7 @@ | |
#include <stdlib.h> | |
#include <string.h> | |
+#include "util.h" | |
#include "memory.h" | |
#include "txtbuf.h" | |
t@@ -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; | |
} | |
} | |
t@@ -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 | |
t@@ -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. | |
*/ | |
t@@ -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. | |
t@@ -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 | |
t@@ -6,6 +6,7 @@ | |
#include <X11/Xutil.h> | |
#include <pango/pangoxft.h> | |
+#include "util.h" | |
#include "memory.h" | |
#include "common.h" | |
#include "txtbuf.h" | |
t@@ -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; | |
t@@ -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; | |
}; | |
t@@ -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; | |
t@@ -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); | |
} | |
t@@ -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… | |
for (size_t i = undo->cap; i < cap; i++) { | |
undo->stack[i].text = NULL; | |
t@@ -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]; | |
} | |
t@@ -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; | |
t@@ -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 | |
t@@ -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 | |
t@@ -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; | |
t@@ -156,7 +167,7 @@ ledit_undo(undo_stack *undo, enum ledit_mode mode, void *c… | |
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) && | |
t@@ -193,7 +204,10 @@ ledit_undo(undo_stack *undo, enum ledit_mode mode, void *… | |
/* 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; | |
} | |
t@@ -210,17 +224,24 @@ ledit_undo(undo_stack *undo, enum ledit_mode mode, void … | |
} | |
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->l… | |
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; | |
t@@ -263,7 +284,9 @@ ledit_redo(undo_stack *undo, enum ledit_mode mode, void *c… | |
} | |
*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; | |
} | |
t@@ -271,5 +294,20 @@ ledit_redo(undo_stack *undo, enum ledit_mode mode, void *… | |
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 | |
t@@ -1,3 +1,10 @@ | |
+#ifndef _UNDO_H_ | |
+#define _UNDO_H_ | |
+ | |
+#include <stddef.h> | |
+#include "common.h" | |
+#include "txtbuf.h" | |
+ | |
/* | |
* This handles undo and redo. | |
* | |
t@@ -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 | |
); | |
/* | |
t@@ -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 | |
); | |
/* | |
t@@ -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 … | |
size_t *cur_line_ret, size_t *cur_index_ret, size_t *min_line_ret | |
); | |
t@@ -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 … | |
size_t *cur_line_ret, size_t *cur_index_ret, size_t *min_line_ret | |
); | |
t@@ -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 | |
t@@ -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->co… | |
- ); | |
- 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 | |
t@@ -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 | |
t@@ -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> | |
t@@ -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" | |
t@@ -75,9 +73,6 @@ static PangoAttrList *get_pango_attributes(ledit_view *view,… | |
*/ | |
static void set_line_layout_attrs(ledit_view *view, size_t line, PangoLayout *… | |
-/* 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); | |
t@@ -90,14 +85,14 @@ static void resize_and_move_line_gap(ledit_view *view, siz… | |
/* 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, si… | |
+view_create(ledit_buffer *buffer, ledit_theme *theme, ledit_mode mode, size_t … | |
if (basic_attrs == NULL) { | |
basic_attrs = pango_attr_list_new(); | |
#if PANGO_VERSION_CHECK(1, 44, 0) | |
t@@ -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 s… | |
view->lines = resize_and_move_gap( | |
view->lines, sizeof(ledit_view_line), | |
view->lines_gap, view->lines_cap, view->lines_num, | |
t@@ -235,10 +229,7 @@ view_notify_delete_text(ledit_view *view, size_t line, si… | |
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) | |
t@@ -278,6 +269,11 @@ view_notify_delete_lines(ledit_view *view, size_t index1,… | |
); | |
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_ga… | |
/* force first entry to offset 0 if first line was deleted */ | |
if (index1 == 0) { | |
ledit_view_line *vl = view_get_line(view, 0); | |
t@@ -329,7 +325,7 @@ set_line_layout_attrs(ledit_view *view, size_t line, Pango… | |
PangoAttrList *list = NULL; | |
if (view->sel_valid) { | |
ledit_range sel = view->sel; | |
- view_sort_selection(&sel.line1, &sel.byte1, &sel.line2, &sel.b… | |
+ 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) { | |
t@@ -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); | |
t@@ -554,10 +551,8 @@ view_next_cursor_pos(ledit_view *view, size_t line, size_… | |
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; | |
t@@ -578,8 +573,6 @@ view_prev_cursor_pos(ledit_view *view, size_t line, size_t… | |
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--) { | |
t@@ -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, … | |
ledit_view_line *vl = view_get_line(view, line); | |
t@@ -1114,9 +1106,9 @@ view_x_softline_to_pos(ledit_view *view, size_t line, in… | |
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 gra… | |
/* 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--; | |
t@@ -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, | |
t@@ -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_in… | |
+ 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); | |
t@@ -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! */ | |
t@@ -1740,8 +1711,8 @@ view_set_selection(ledit_view *view, size_t line1, size_… | |
} | |
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.lin… | |
+ sort_range(&l1_new, &b1_new, &l2_new, &b2_new); | |
+ sort_range(&view->sel.line1, &view->sel.byte1, &view->sel.line2, &view… | |
/* 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++) { | |
t@@ -1772,6 +1743,7 @@ view_set_selection(ledit_view *view, size_t line1, size_… | |
view->sel.line2 = line2; | |
view->sel.byte2 = byte2; | |
view->sel_valid = 1; | |
+ view->redraw = 1; | |
} | |
static void | |
t@@ -1815,8 +1787,8 @@ view_button_handler(void *data, XEvent *event) { | |
view_wipe_line_cursor_attrs(view, view->cur_li… | |
/* FIXME: return to old mode afterwards? */ | |
/* should change_mode_group even be called her… | |
- view_set_mode(view, VISUAL); | |
} | |
+ view_set_mode(view, VISUAL); | |
if (!view->sel_valid) { | |
/* the selection has just started, so the curr… | |
position is already set to the beginning of… | |
t@@ -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_inde… | |
- 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->c… | |
+ 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_ind… | |
+ if (s != UNDO_NORMAL) { | |
+ window_show_message(view->window, undo_state_to_str(s)… | |
+ 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_inde… | |
- 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->c… | |
+ 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_ind… | |
+ if (s != UNDO_NORMAL) { | |
+ window_show_message(view->window, undo_state_to_str(s)… | |
+ 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 | |
t@@ -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" | |
t@@ -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 … | |
char sel_valid; /* whether there is currently a valid select… | |
char redraw; /* whether something has changed so the view… | |
t@@ -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'. | |
t@@ -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 | |
); | |
/* | |
t@@ -442,11 +449,6 @@ void view_scroll_to_pos_bottom(ledit_view *view, size_t l… | |
void view_ensure_cursor_shown(ledit_view *view); | |
/* | |
- * Sort the given range so that (*line1 < *line2) or (*line1 == *line2 && *byt… | |
- */ | |
-void view_sort_selection(size_t *line1, size_t *byte1, size_t *line2, size_t *… | |
- | |
-/* | |
* Clear the selection. | |
*/ | |
void view_wipe_selection(ledit_view *view); | |
t@@ -465,20 +467,20 @@ void view_set_selection(ledit_view *view, size_t line1, … | |
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 | |
t@@ -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 { | |
t@@ -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, w… | |
+ /* 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, c… | |
} | |
} | |
t@@ -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) { | |
t@@ -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; | |
t@@ -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 | |
t@@ -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). | |
t@@ -5,10 +8,17 @@ | |
* partially here and partially in keys_command, but that's the way it is for … | |
*/ | |
-#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; | |
t@@ -42,7 +52,7 @@ typedef struct { | |
int message_shown; /* whether a readonly message is shown at the… | |
bottom_bar *bb; /* encapsulates the text at the bottom */ | |
int redraw; /* whether something has changed and the wind… | |
- enum ledit_mode mode; /* mode of the view - a bit ugly to duplicate… | |
+ ledit_mode mode; /* mode of the view - a bit ugly to duplicate this… | |
/* stuff for filtering events so not too many have to be handled */ | |
struct timespec last_scroll; | |
t@@ -83,7 +93,7 @@ typedef struct { | |
/* | |
* Create a window with initial mode 'mode'. | |
*/ | |
-ledit_window *window_create(ledit_common *common, ledit_theme *theme, enum led… | |
+ledit_window *window_create(ledit_common *common, ledit_theme *theme, ledit_mo… | |
/* | |
* Destroy a window. | |
t@@ -171,6 +181,8 @@ void window_set_bottom_bar_realtext(ledit_window *window, … | |
/* | |
* 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); | |
t@@ -183,6 +195,10 @@ void window_show_message(ledit_window *window, char *text… | |
* 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, ...); | |
t@@ -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. |