commit 4ee5ad27da617738b33a8e5e46aa75afcc2e54d0
parent a3b601c93fde71dd598f855980897a1e3ead492e
Author: lumidify <nobody@lumidify.org>
Date: Thu, 5 Oct 2023 12:10:03 +0200
Add basic uppercase/lowercase support
Diffstat:
5 files changed, 118 insertions(+), 13 deletions(-)
diff --git a/Makefile b/Makefile
@@ -13,6 +13,7 @@ MISCFILES = Makefile README LICENSE IDEAS NOTES TODO
DEBUG=0
SANITIZE=0
+ENABLE_UTF8PROC=1
OBJ = \
assert.o \
@@ -70,12 +71,15 @@ EXTRA_CFLAGS_DEBUG0 = ${CFLAGS}
EXTRA_LDFLAGS_DEBUG0 = ${LDFLAGS}
EXTRA_CFLAGS_DEBUG1 = -DLEDIT_DEBUG -g
EXTRA_FLAGS_SANITIZE1 = -fsanitize=address
+EXTRA_CFLAGS_UTF8PROC0 = -DENABLE_UTF8PROC=0
+EXTRA_CFLAGS_UTF8PROC1 = `pkg-config --cflags libutf8proc` -DENABLE_UTF8PROC=1
+EXTRA_LDFLAGS_UTF8PROC1 = `pkg-config --libs libutf8proc`
# Xcursor isn't actually needed right now since I'm not using the drag 'n drop functionality
# of ctrlsel yet, but since it's moderately likely that I will use that in the future, I
# decided to just leave it in.
-CFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_CFLAGS_DEBUG${DEBUG}} -Wall -Wextra -pedantic -D_POSIX_C_SOURCE=200809L -std=c99 `pkg-config --cflags x11 xkbfile pangoxft xext xcursor`
-LDFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_LDFLAGS_DEBUG${DEBUG}} `pkg-config --libs x11 xkbfile pangoxft xext xcursor` -lm
+CFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_CFLAGS_DEBUG${DEBUG}} ${EXTRA_CFLAGS_UTF8PROC${ENABLE_UTF8PROC}} -Wall -Wextra -pedantic -D_POSIX_C_SOURCE=200809L -std=c99 `pkg-config --cflags x11 xkbfile pangoxft xext xcursor`
+LDFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_LDFLAGS_DEBUG${DEBUG}} ${EXTRA_LDFLAGS_UTF8PROC${ENABLE_UTF8PROC}} `pkg-config --libs x11 xkbfile pangoxft xext xcursor` -lm
all: ${BIN}
diff --git a/README b/README
@@ -8,15 +8,18 @@ layout.
Additionally, it allows multiple views on a text buffer so that different
parts of a file can be shown at the same time.
-REQUIREMENTS: pango (with xft), xlib (extensions: xkb, xdbe), xcursor
+REQUIREMENTS:
+pango (with xft), xlib (extensions: xkb, xdbe),
+xcursor, libutf8proc (if ENABLE_UTF8PROC is set)
Note: xcursor technically wouldn't be needed since it's just a dependency
of features in ctrlsel that currently aren't being used, but I will
probably use those features later, so I was too lazy to remove those parts.
Packages to install:
-On OpenBSD: pango
-On MX Linux: libpango1.0-dev, libx11-dev, libxkbfile-dev libxcursor-dev
+On OpenBSD: pango, libutf8proc
+On Debian-based systems:
+libpango1.0-dev, libx11-dev, libxkbfile-dev libxcursor-dev, libutf8proc-dev
(this is just from memory, I need to test it with a fresh system sometime)
Installation:
diff --git a/keys_basic.c b/keys_basic.c
@@ -17,6 +17,7 @@
/* FIXME: sort functions a bit better, maybe split file */
/* FIXME: documentation */
#include <stdio.h>
+#include <ctype.h>
#include <stdlib.h>
#include <X11/Xlib.h>
@@ -27,6 +28,10 @@
#include <X11/XF86keysym.h>
#include <X11/cursorfont.h>
+#if ENABLE_UTF8PROC
+#include "utf8proc.h"
+#endif
+
#include "util.h"
#include "assert.h"
#include "memory.h"
@@ -121,6 +126,8 @@ static struct action delete_graphemes_forwards_multiline(ledit_view *view, char
static struct action delete_graphemes_backwards_multiline(ledit_view *view, char *text, size_t len);
static struct action yank(ledit_view *view, char *text, size_t len);
static struct action yank_lines(ledit_view *view, char *text, size_t len);
+static struct action uppercase(ledit_view *view, char *text, size_t len);
+static struct action lowercase(ledit_view *view, char *text, size_t len);
static struct action replace(ledit_view *view, char *text, size_t len);
static struct action cursor_to_first_non_ws(ledit_view *view, char *text, size_t len);
static struct action join_lines(ledit_view *view, char *text, size_t len);
@@ -160,6 +167,7 @@ static struct basic_key_cb basic_key_cb_map[] = {
{"append-after-eol", &append_after_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT},
{"append-line-above", &append_line_above, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT},
{"append-line-below", &append_line_below, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT},
+ {"break-line", &break_line, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
{"change", &change, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|VISUAL|INSERT},
{"change-to-eol", &change_to_eol, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
{"clipboard-copy", &clipcopy, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
@@ -185,17 +193,17 @@ static struct basic_key_cb basic_key_cb_map[] = {
{"enter-searchedit-backwards", &enter_searchedit_backward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT|VISUAL},
{"enter-searchedit-forwards", &enter_searchedit_forward, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT|VISUAL},
{"enter-visual", &enter_visual, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
- {"return-to-normal", &return_to_normal, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
{"find-char-backwards", &find_char_backwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
{"find-char-forwards", &find_char_forwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
{"find-next-char-backwards", &find_next_char_backwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
{"find-next-char-forwards", &find_next_char_forwards, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
{"insert-at-beginning", &insert_at_beginning, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
+ {"insert-mark", &insert_mark, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
{"insert-text", &insert_mode_insert_text, KEY_FLAG_JUMP_TO_CURSOR, INSERT},
{"join-lines", &join_lines, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
{"jump-to-mark", &jump_to_mark, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
{"key-0", &key_0, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
- {"insert-mark", &insert_mark, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
+ {"lowercase", &lowercase, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
{"move-to-eol", &move_to_eol, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
{"move-to-line", &move_to_line, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
{"next-bigword", &next_bigword, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
@@ -219,7 +227,7 @@ static struct basic_key_cb basic_key_cb_map[] = {
{"redo", &redo, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
{"repeat-command", &repeat_command, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
{"replace", &replace, KEY_FLAG_JUMP_TO_CURSOR, NORMAL},
- {"break-line", &break_line, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
+ {"return-to-normal", &return_to_normal, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
{"screen-down", &screen_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT},
{"screen-up", &screen_up, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT},
{"scroll-lines-down", &scroll_lines_down, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT},
@@ -232,6 +240,7 @@ static struct basic_key_cb basic_key_cb_map[] = {
{"switch-selection-end", &switch_selection_end, KEY_FLAG_JUMP_TO_CURSOR, VISUAL},
{"toggle-hard-line-based", &toggle_hard_line_based, KEY_FLAG_NONE|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
{"undo", &undo, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
+ {"uppercase", &uppercase, KEY_FLAG_JUMP_TO_CURSOR, NORMAL|INSERT},
{"yank", &yank, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|VISUAL|INSERT},
{"yank-lines", &yank_lines, KEY_FLAG_JUMP_TO_CURSOR|KEY_FLAG_LOCK_ALLOWED, NORMAL|INSERT},
};
@@ -2495,6 +2504,80 @@ GEN_MOVE_TO_CHAR(
)
static struct action
+loweruppercase(ledit_view *view, int upper) {
+ /* FIXME: shouldn't CHECK_VIEW_LOCKED be in a lot more places? */
+ CHECK_VIEW_LOCKED(view);
+ /* FIXME: move most of this to convenience functions in buffer.c */
+ ledit_line *line = buffer_get_line(view->buffer, view->cur_line);
+ if (view->cur_index >= line->len)
+ return (struct action){ACTION_NONE, NULL};
+ buffer_normalize_line(line);
+ size_t start_index = view->cur_index;
+#if ENABLE_UTF8PROC
+ utf8proc_int32_t c;
+ /* FIXME: this cast to utf8proc_uint8_t probably doesn't break anything, but could it? */
+ utf8proc_ssize_t origlen = utf8proc_iterate((utf8proc_uint8_t *)line->text + start_index, line->len - start_index, &c);
+ /* FIXME: show error message? */
+ if (c < 0 || origlen < 1)
+ return (struct action){ACTION_NONE, NULL};
+ utf8proc_int32_t u;
+ if (upper)
+ u = utf8proc_toupper(c);
+ else
+ u = utf8proc_tolower(c);
+ utf8proc_uint8_t u8[4];
+ utf8proc_ssize_t newlen = utf8proc_encode_char(u, u8);
+ if (newlen < 1)
+ return (struct action){ACTION_NONE, NULL};
+ delete_range(
+ view, 0, 0,
+ view->cur_line, start_index, view->cur_line, start_index + origlen, 0
+ );
+ insert_text(
+ view, view->cur_line, start_index, (char *)u8, newlen,
+ view->cur_line, start_index, view->cur_line, start_index, 1, 1, 0
+ );
+#else
+ char c;
+ if (upper)
+ c = toupper((unsigned char)line->text[view->cur_index]);
+ else
+ c = tolower((unsigned char)line->text[view->cur_index]);
+ delete_range(
+ view, 0, 0,
+ view->cur_line, start_index, view->cur_line, start_index + 1, 0
+ );
+ insert_text(
+ view, view->cur_line, start_index, &c, 1,
+ view->cur_line, start_index, view->cur_line, start_index, 1, 1, 0
+ );
+#endif
+ /* If the last character on a line is replaced, the cursor would jump
+ backwards due to the deletion, so it has to be set to the original
+ position again */
+ view->cur_index = start_index;
+ push_undo_empty_insert(view, view->cur_line, view->cur_index, 0);
+ if (view->mode == NORMAL)
+ view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
+ finalize_repetition_stack();
+ return (struct action){ACTION_NONE, NULL};
+}
+
+static struct action
+uppercase(ledit_view *view, char *text, size_t len) {
+ (void)text;
+ (void)len;
+ return loweruppercase(view, 1);
+}
+
+static struct action
+lowercase(ledit_view *view, char *text, size_t len) {
+ (void)text;
+ (void)len;
+ return loweruppercase(view, 0);
+}
+
+static struct action
replace_cb(ledit_view *view, char *text, size_t len) {
CHECK_VIEW_LOCKED(view);
size_t start_index = view->cur_index;
@@ -2512,11 +2595,13 @@ replace_cb(ledit_view *view, char *text, size_t len) {
view, view->cur_line, start_index, text, len,
view->cur_line, start_index, view->cur_line, start_index, 1, 1, 0
);
- /* this should not be necessary, but just in case */
- view->cur_index = view_get_legal_normal_pos(
- view, view->cur_line, view->cur_index
- );
+ /* If the last character on a line is replaced, the cursor would jump
+ backwards due to the deletion, so it has to be set to the original
+ position again */
+ view->cur_index = start_index;
push_undo_empty_insert(view, view->cur_line, view->cur_index, 0);
+ if (view->mode == NORMAL)
+ view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
grab_char_cb = NULL;
finalize_repetition_stack();
return (struct action){ACTION_NONE, NULL};
diff --git a/leditrc.5 b/leditrc.5
@@ -1,4 +1,4 @@
-.Dd October 2, 2023
+.Dd October 5, 2023
.Dt LEDITRC 5
.Os
.Sh NAME
@@ -674,6 +674,17 @@ write, and current line number.
Switch the end of the selection that can be moved.
.It Ar toggle-hard-line-based Op normal, visual, insert
Toggle the line mode between hardline and softline.
+.It Ar uppercase Op normal, insert
+.It Ar lowercase Op normal, insert
+Replace the character at the current cursor position with the uppercase/lowercase version if it exists.
+If utf8proc support is not enabled, this will use the standard C library functions
+.Fn toupper
+and
+.Fn tolower ,
+so it will not work with most Unicode characters.
+Note that even with utf8proc, it will not work in all cases because some characters require
+more complex handling (e.g. characters that require multiple characters when converted to
+uppercase), which is not supported.
.It Ar undo Oo normal, insert Oc Oo num Oc
Undo
.Ar num
diff --git a/leditrc.example b/leditrc.example
@@ -107,6 +107,8 @@ bindings = {
bind find-next-char-backwards text "T" modes normal|visual
bind find-char-forwards text "f" modes normal|visual
bind find-char-backwards text "F" modes normal|visual
+ bind uppercase text "U" modes normal|insert mods control
+ bind lowercase text "L" modes normal|insert mods control
bind insert-text catchall modes insert
}
command-keys = {