commit 123a3087ad0bc4f772f0cd0a17caf95304092f87
parent 82723082181a863b7bc7c7cbbcc92da5c30caf6d
Author: lumidify <nobody@lumidify.org>
Date:   Wed,  8 Dec 2021 20:56:21 +0100
Start implementing substitution
Diffstat:
7 files changed, 181 insertions(+), 3 deletions(-)
diff --git a/buffer.c b/buffer.c
@@ -143,6 +143,22 @@ buffer_create(ledit_common *common) {
 	return buffer;
 }
 
+void
+buffer_lock_all_views_except(ledit_buffer *buffer, ledit_view *view, char *lock_text) {
+	for (size_t i = 0; i < buffer->views_num; i++) {
+		if (buffer->views[i] != view) {
+			view_lock(buffer->views[i], lock_text);
+		}
+	}
+}
+
+void
+buffer_unlock_all_views(ledit_buffer *buffer) {
+	for (size_t i = 0; i < buffer->views_num; i++) {
+		view_unlock(buffer->views[i]);
+	}
+}
+
 static void
 set_view_hard_line_text(ledit_buffer *buffer, ledit_view *view) {
 	char *text = buffer->hard_line_based ? "|HL" : "|SL";
diff --git a/buffer.h b/buffer.h
@@ -49,6 +49,16 @@ struct ledit_buffer {
 ledit_buffer *buffer_create(ledit_common *common);
 
 /*
+ * Lock all views except the given view.
+ */
+void buffer_lock_all_views_except(ledit_buffer *buffer, ledit_view *view, char *lock_text);
+
+/*
+ * Unlock all views.
+ */
+void buffer_unlock_all_views(ledit_buffer *buffer);
+
+/*
  * Set the hard line mode of the buffer and update the
  * displayed mode in all views.
  */
diff --git a/keys.c b/keys.c
@@ -27,7 +27,11 @@ get_language_index(char *lang) {
 }
 
 /* FIXME: Does this break anything? */
-static unsigned int importantmod = ShiftMask | ControlMask | Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask;
+/*static unsigned int importantmod = ShiftMask | ControlMask | Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask;*/
+/* FIXME: ShiftMask is currently masked away anyways, so it isn't really important */
+/* 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
diff --git a/keys_command.c b/keys_command.c
@@ -27,6 +27,18 @@
 #include "keys_command.h"
 #include "keys_command_config.h"
 
+static char *last_search = NULL;
+static char *last_replacement = NULL;
+static int last_replacement_global = 0;
+
+static int
+view_locked_error(ledit_view *view) {
+	window_show_message(view->window, view->lock_text, -1);
+	return 0;
+}
+
+#define CHECK_VIEW_LOCKED if (view->lock_text) return view_locked_error(view)
+
 /* FIXME: history for search and commands */
 
 static int create_view(ledit_view *view, char *cmd, size_t l1, size_t l2);
@@ -104,7 +116,107 @@ handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2) {
 	(void)cmd;
 	(void)l1;
 	(void)l2;
-	printf("substitute\n");
+	CHECK_VIEW_LOCKED;
+	cmd++; /* remove 's' at beginning */
+	size_t len = strlen(cmd);
+	if (len == 0) goto error;
+	/* FIXME: utf8 */
+	char sep = cmd[0];
+	cmd++;
+	char *next = strchr(cmd, sep);
+	if (next == NULL) goto error;
+	*next = '\0';
+	next++;
+	char *last = strchr(next, sep);
+	if (last == NULL) goto error;
+	*last = '\0';
+	last++;
+	int confirm = 0, global = 0;
+	char *c = last;
+	while (*c != '\0') {
+		switch (*c) {
+		case 'c':
+			confirm = 1;
+			break;
+		case 'g':
+			global = 1;
+			break;
+		default:
+			goto error;
+		}
+		c++;
+	}
+	free(last_search);
+	free(last_replacement);
+	last_search = ledit_strdup(cmd);
+	last_replacement = ledit_strdup(next);
+	last_replacement_global = global;
+
+	if (confirm) {
+		buffer_lock_all_views_except(view->buffer, view, "Ongoing substitution in other view.");
+		buffer_unlock_all_views(view->buffer);
+	} else {
+		int num = 0;
+		int start_undo_group = 1;
+		size_t slen = strlen(last_search);
+		size_t rlen = strlen(last_replacement);
+		txtbuf *buf = txtbuf_new(); /* FIXME: don't allocate new every time */
+		view_wipe_line_cursor_attrs(view, view->cur_line);
+		for (size_t i = l1 - 1; i < l2; i++) {
+			ledit_line *ll = buffer_get_line(view->buffer, i);
+			buffer_normalize_line(ll);
+			char *pos = strstr(ll->text, last_search);
+			while (pos != NULL) {
+				size_t index = (size_t)(pos - ll->text);
+				ledit_range cur_range, del_range;
+				cur_range.line1 = view->cur_line;
+				cur_range.byte1 = view->cur_line;
+				view_delete_range(
+				    view, DELETE_CHAR,
+				    i, index,
+				    i, index + slen,
+				    &view->cur_line, &view->cur_index,
+				    &del_range, buf
+				);
+				cur_range.line2 = view->cur_line;
+				cur_range.byte2 = view->cur_index;
+				undo_push_delete(
+				    view->buffer->undo, buf, del_range, cur_range, start_undo_group, view->mode
+				);
+				start_undo_group = 0;
+				txtbuf ins_buf = {.text = last_replacement, .len = rlen, .cap = rlen};
+				cur_range.line1 = view->cur_line;
+				cur_range.byte1 = view->cur_index;
+				del_range.line1 = i;
+				del_range.byte1 = index;
+				size_t cur_line, cur_index;
+				buffer_insert_text_with_newlines(
+				    view->buffer, i, index, last_replacement, rlen,
+				    &cur_line, &cur_index
+				);
+				cur_range.line2 = view->cur_line;
+				cur_range.byte2 = view->cur_index;
+				del_range.line2 = cur_line;
+				del_range.byte2 = cur_index;
+				undo_push_insert(
+				    view->buffer->undo, &ins_buf, del_range, cur_range, 0, view->mode
+				);
+				num++;
+				if (!global) break;
+				buffer_normalize_line(ll); /* just in case */
+				pos = strstr(ll->text + index + rlen, last_search);
+			}
+		}
+		/* FIXME: show number replaced */
+		/* this doesn't need to be added to the undo stack since it's called on undo/redo anyways */
+		view->cur_index = view_get_legal_normal_pos(view, view->cur_line, view->cur_index);
+		view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
+		view_ensure_cursor_shown(view);
+		txtbuf_destroy(buf);
+	}
+	return 0;
+error:
+	window_show_message(view->window, "Invalid command", -1);
 	return 0;
 }
 
@@ -133,6 +245,7 @@ $ last line
 */
 
 /* FIXME: ACTUALLY USE LEN!!! */
+/* FIXME: allow using marks and selection range here */
 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) {
 	(void)len;
@@ -180,6 +293,7 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin
 				l1 = 1;
 				l2 = view->lines_num;
 				*l1_valid = *l2_valid = 1;
+				c++;
 				break;
 			} else {
 				return 1;
@@ -204,6 +318,8 @@ parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *lin
 	}
 	if ((!*l1_valid || !*l2_valid) && !(s & START_RANGE))
 		return 1;
+	if ((*l1_valid || *l2_valid) && (l1 == 0 || l2 == 0 || l1 > view->lines_num || l2 > view->lines_num))
+		return 1; /* FIXME: better error messages */
 	*cmd_ret = c;
 	*line1_ret = l1;
 	*line2_ret = l2;
@@ -221,6 +337,7 @@ handle_cmd(ledit_view *view, char *cmd, size_t len) {
 	if (parse_range(view, cmd, len, &c, &l1, &l2, &l1_valid, &l2_valid))
 		return 0;
 	int range_given = l1_valid && l2_valid;
+	/* FIXME: mandatory range */
 	for (size_t i = 0; i < LENGTH(cmds); i++) {
 		if (!strncmp(cmds[i].cmd, c, strlen(cmds[i].cmd)) &&
 		    (!range_given || cmds[i].type == CMD_OPTIONAL_RANGE)) {
@@ -335,6 +452,7 @@ search_next(ledit_view *view) {
 	view_wipe_line_cursor_attrs(view, view->cur_line);
 	enum ledit_search_state ret = ledit_search_next(view, &view->cur_line, &view->cur_index);
 	view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
+	view_ensure_cursor_shown(view);
 	if (ret != SEARCH_NORMAL)
 		window_show_message(view->window, search_state_to_str(ret), -1);
 }
@@ -344,6 +462,7 @@ search_prev(ledit_view *view) {
 	view_wipe_line_cursor_attrs(view, view->cur_line);
 	enum ledit_search_state ret = ledit_search_prev(view, &view->cur_line, &view->cur_index);
 	view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
+	view_ensure_cursor_shown(view);
 	if (ret != SEARCH_NORMAL)
 		window_show_message(view->window, search_state_to_str(ret), -1);
 }
diff --git a/search.c b/search.c
@@ -19,7 +19,7 @@
 #include "search.h"
 
 /* FIXME: make sure only whole utf8 chars are matched */
-char *last_search = NULL;
+static char *last_search = NULL;
 enum {
 	FORWARD,
 	BACKWARD
diff --git a/view.c b/view.c
@@ -112,6 +112,7 @@ view_create(ledit_buffer *buffer, ledit_theme *theme, enum ledit_mode mode, size
 	view->window = window_create(buffer->common, theme, mode);
 	view->theme = theme;
 	view->cache = cache_create(buffer->common->dpy);
+	view->lock_text = NULL;
 	view->cur_action = (struct action){ACTION_NONE, NULL};
 	window_set_scroll_callback(view->window, &view_scroll_handler, view);
 	window_set_button_callback(view->window, &view_button_handler, view);
@@ -147,6 +148,18 @@ view_create(ledit_buffer *buffer, ledit_theme *theme, enum ledit_mode mode, size
 	return view;
 }
 
+void
+view_lock(ledit_view *view, char *lock_text) {
+	free(view->lock_text);
+	view->lock_text = ledit_strdup(lock_text);
+}
+
+void
+view_unlock(ledit_view *view) {
+	free(view->lock_text);
+	view->lock_text = NULL;
+}
+
 ledit_view_line *
 view_get_line(ledit_view *view, size_t index) {
 	assert(index < view->lines_num);
@@ -262,6 +275,7 @@ void
 view_destroy(ledit_view *view) {
 	cache_destroy(view->cache);
 	window_destroy(view->window);
+	free(view->lock_text);
 	free(view->lines);
 	free(view);
 }
diff --git a/view.h b/view.h
@@ -57,6 +57,7 @@ struct ledit_view {
 	ledit_theme *theme;       /* current theme in use */
 	ledit_cache *cache;       /* cache for pixmaps and pango layouts */
 	ledit_view_line *lines;   /* array of lines, stored as gap buffer */
+	char *lock_text;          /* text to show if view is locked, i.e. no edits allowed */
 	/* current command type - used by key handler in keys_command.c */
 	enum ledit_command_type cur_command_type;
 	struct action cur_action; /* current action to execute on key press */
@@ -98,6 +99,20 @@ ledit_view *view_create(
 );
 
 /*
+ * Lock a view.
+ * Views are locked for instance when substitution with confirmation is
+ * being performed in another view to avoid an inconsistent state.
+ * This currently only sets the lock text - commands using the view need
+ * to make sure to check that it isn't locked.
+ */
+void view_lock(ledit_view *view, char *text);
+
+/*
+ * Unlock a view.
+ */
+void view_unlock(ledit_view *view);
+
+/*
  * Get the view line at the given index.
  */
 ledit_view_line *view_get_line(ledit_view *view, size_t index);