shithub: orca

Download patch

ref: cd3d99b944f1be8b5eae83d9347557f768fed40a
parent: 4c06284d5e66f17c06ec132607c0237ee26ce235
author: Sigrid Haflínudóttir <ftrvxmtrx@gmail.com>
date: Sun Feb 23 09:33:35 EST 2020

plan9: undo/redo

--- a/orca.man
+++ b/orca.man
@@ -16,6 +16,8 @@
 ] [
 .I -c cursor
 ] [
+.I -l undo_limit
+] [
 .I file
 ]
 .SH DESCRIPTION
@@ -61,6 +63,9 @@
 .TP
 .B -c
 Sets the cursor character.
+.TP
+.B -l
+Sets the maximum number of undo steps. Default is 100.
 .PP
 .SH KEYS
 .TP
@@ -98,6 +103,12 @@
 .TP
 .B Ctrl+v
 Paste from the snarf buffer.
+.TP
+.B Ctrl+z
+Undo.
+.TP
+.B Ctrl+y
+Redo.
 .TP
 .B Ctrl+u Ctrl+l
 Convert selected text to upper/lower case.
--- a/plan9.c
+++ b/plan9.c
@@ -55,6 +55,16 @@
 	Mark_flag_selected = 1<<7,
 };
 
+typedef struct Snap Snap;
+
+struct Snap {
+	int bpm, apm;
+	vlong tick;
+	Point cur, scroll;
+	Rectangle sel;
+	Field field;
+};
+
 static int bpm = 120, apm = 120;
 static Point rulers = {8, 8};
 static int rulerstyle = Sfancy, dotstyle = Sfancy;
@@ -80,6 +90,10 @@
 static long framedev; /* frame deviation in ms */
 static char *shellcmd;
 
+static Snap *snaps;
+static int numsnaps, nextsnap;
+static int maxsnaps = 100;
+
 static struct {
 	struct {
 		char *menu;
@@ -138,6 +152,73 @@
 	.item = menu3i,
 };
 
+static void
+snapshot(void)
+{
+	Snap *s;
+	int i;
+
+	if (maxsnaps < 1)
+		return;
+
+	if (nextsnap >= maxsnaps) {
+		nextsnap--;
+		field_deinit(&snaps->field);
+		memmove(snaps, snaps+1, sizeof(Snap)*nextsnap);
+	}
+	s = &snaps[nextsnap++];
+	s->bpm = bpm;
+	s->apm = apm;
+	s->tick = tick;
+	s->cur = cur;
+	s->scroll = scroll;
+	s->sel = sel;
+	for (i = nextsnap; i < numsnaps; i++)
+		field_deinit(&snaps[i].field);
+	field_init(&s->field);
+	field_copy(&field, &s->field);
+	numsnaps = nextsnap;
+}
+
+static void
+gotosnapshot(int i)
+{
+	Snap *s;
+
+	s = &snaps[i];
+	bpm = s->bpm;
+	apm = s->apm;
+	tick = s->tick;
+	cur = s->cur;
+	scroll = s->scroll;
+	sel = s->sel;
+	field_copy(&s->field, &field);
+}
+
+static void
+undo(void)
+{
+	if (nextsnap < 1)
+		return;
+
+	if (nextsnap == numsnaps) {
+		snapshot();
+		nextsnap--;
+	}
+	gotosnapshot(--nextsnap);
+}
+
+static void
+redo(void)
+{
+	if (nextsnap >= numsnaps-1)
+		return;
+
+	gotosnapshot(++nextsnap);
+	if (nextsnap == numsnaps)
+		nextsnap--;
+}
+
 Usz
 orca_round_up_power2(Usz x)
 {
@@ -888,6 +969,7 @@
 		if ((n = read(0, buf, sizeof(buf)-1)) <= 0)
 			break;
 
+		snapshot();
 		for (i = 0; i < n; i++) {
 			if (buf[i] == '\r' || buf[i] == '\n' || buf[i] == 0) {
 				buf[i] = 0;
@@ -1012,7 +1094,7 @@
 static void
 usage(void)
 {
-	print("usage: %s [-p] [-b bpm] [-c cursor] [-s WxH] [-r random_seed] [-i ip_address] [-u udp_port] [-m midi_path] [file]\n", argv0);
+	print("usage: %s [-p] [-b bpm] [-c cursor] [-l undo_limit] [-s WxH] [-r random_seed] [-i ip_address] [-u udp_port] [-m midi_path] [file]\n", argv0);
 	threadexitsall("usage");
 }
 
@@ -1085,6 +1167,12 @@
 		free(midipath);
 		midipath = EARGF(usage());
 		break;
+	case 'l':
+		if ((maxsnaps = atoi(EARGF(usage()))) < 0) {
+			fprint(2, "invalid undo limit %s\n", EARGF(usage()));
+			threadexitsall("args");
+		}
+		break;
 	default:
 		usage();
 	}ARGEND
@@ -1157,6 +1245,8 @@
 	netdial(nil, nil);
 	midiopen(nil);
 
+	snaps = calloc(maxsnaps, sizeof(Snap));
+
 	for (;;) {
 		redraw(complete);
 		complete = false;
@@ -1241,8 +1331,10 @@
 				if (shiftdown || mode == Mselect)
 					selext(0, -move.y);
 				else {
-					if (altdown || mode == Mslide)
+					if (altdown || mode == Mslide) {
+						snapshot();
 						selmove(0, -move.y);
+					}
 					curmove(0, -move.y);
 				}
 				break;
@@ -1250,8 +1342,10 @@
 				if (shiftdown || mode == Mselect)
 					selext(0, move.y);
 				else {
-					if (altdown || mode == Mslide)
+					if (altdown || mode == Mslide) {
+						snapshot();
 						selmove(0, move.y);
+					}
 					curmove(0, move.y);
 				}
 				break;
@@ -1259,20 +1353,21 @@
 				if (shiftdown || mode == Mselect) {
 					selext(-move.x, 0);
 				} else {
-					if (altdown || mode == Mslide)
+					if (altdown || mode == Mslide) {
+						snapshot();
 						selmove(-move.x, 0);
+					}
 					curmove(-move.x, 0);
 				}
 				break;
-			case 0x0c: /* C-l */
-				selmap(tolower);
-				break;
 			case Kright:
 				if (shiftdown || mode == Mselect)
 					selext(move.x, 0);
 				else {
-					if (altdown || mode == Mslide)
+					if (altdown || mode == Mslide) {
+						snapshot();
 						selmove(move.x, 0);
+					}
 					curmove(move.x, 0);
 				}
 				break;
@@ -1312,6 +1407,7 @@
 					snprint(filename, sizeof(filename), "%s", tmp);
 				break;
 			case 0x18: /* C-x */
+				snapshot();
 				selcopy();
 				selset('.');
 				break;
@@ -1319,6 +1415,7 @@
 				selcopy();
 				break;
 			case 0x16: /* C-v */
+				snapshot();
 				selpaste();
 				break;
 			case '[':
@@ -1338,15 +1435,19 @@
 				complete = true;
 				break;
 			case '(':
+				snapshot();
 				w = snaplow(w, rulers.x);
 				break;
 			case ')':
+				snapshot();
 				w = snaphigh(w, rulers.x);
 				break;
 			case '_':
+				snapshot();
 				h = snaplow(h, rulers.y);
 				break;
 			case '+':
+				snapshot();
 				h = snaphigh(h, rulers.y);
 				break;
 			case '>':
@@ -1361,6 +1462,7 @@
 				break;
 			case 0x0b: /* C-k */
 				tmp[0] = 0;
+				snapshot();
 				if (enter("command:", tmp, sizeof(tmp), nil, &kctl, nil) > 0)
 					command(tmp);
 				break;
@@ -1375,6 +1477,16 @@
 			case Kack: /* C-f */
 				forward = true;
 				break;
+			case 0x1a: /* C-z */
+				undo();
+				w = field.width;
+				h = field.height;
+				break;
+			case 0x19:
+				redo();
+				w = field.width;
+				h = field.height;
+				break;
 			case '`':
 			case '~':
 			case L'´':
@@ -1384,9 +1496,15 @@
 				mode = mode != Mselect ? Mselect : Minsert;
 				break;
 			case Knack: /* C-u */
+				snapshot();
 				selmap(toupper);
 				break;
+			case 0x0c: /* C-l */
+				snapshot();
+				selmap(tolower);
+				break;
 			case Kbs: /* C-h */
+				snapshot();
 				if (mode != Mappend) {
 					selset('.');
 				} else {
@@ -1403,6 +1521,7 @@
 				if (r == Kdel || r == ' ')
 					r = '.';
 				if (orca_is_valid_glyph(r)) {
+					snapshot();
 					if (mode != Mappend) {
 						selset(r);
 					} else {