Introduction
Introduction Statistics Contact Development Disclaimer Help
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.
You are viewing proxied material from lumidify.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.