From eebc645dee0d15871d6cc46f156d424cd916b191 Mon Sep 17 00:00:00 2001
From: KatolaZ <katolaz@freaknet.org>
Date: Tue, 30 Jul 2019 12:15:54 +0100
Subject: yank buffer and initial copy/cut/paste support

---
 Makefile   |   2 +-
 TODO       |  11 +++---
 draw.c     |  11 ++++++
 files.c    |   6 ++--
 gramscii.1 |  20 +++++++++--
 gramscii.h |  36 ++++++++++++++-----
 lineset.c  | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 main.c     |  11 +++---
 screen.c   | 117 ++++++++++++++++++++-----------------------------------------
 9 files changed, 227 insertions(+), 104 deletions(-)
 create mode 100644 lineset.c

diff --git a/Makefile b/Makefile
index b88dbf5..dd32b20 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@
 
 include config.mk
 
-SRC = main.c draw.c screen.c files.c
+SRC = main.c draw.c screen.c files.c lineset.c
 INC = config.h gramscii.h
 
 all: options gramscii
diff --git a/TODO b/TODO
index c04d8f3..71689fc 100644
--- a/TODO
+++ b/TODO
@@ -6,8 +6,8 @@
 - use [ENTER] to exit from text insert
 - maybe move "text" mode to "t"
 - implement ellipse
-- filled box (B)
-- manage fill character (as for other styles)
+- (?) filled box (B)
+- (?) manage filled box character (as for other styles)
 - implement comment (#: ignore until the end of the line)
 + parse control characters 
   + parse arrows (text-mode will allow movements as well)
@@ -15,15 +15,18 @@
 - (?) remove extra blanks until EOL when saving to file
 + visual selection
   - crop-to
-  - yank/put
+  * yank
   * fill
-  * delete
+  * cut 
 - undo (by storing lines changed across insert/remove operations)
 - manage special chars (DEL/CANC) during text insert
   (also do not print unmanaged chars!)
 - allow scrolling (both vertical and horizontal)
 - catch SIGWINCH and react appropriately (after scrolling is 
   enabled)
+* put yanked content (p)
+* turn screen into a lineset
+* change alloc/ensure functions to work on line_t* and lineset_t*
 * add crop command (C)
 * reorganise code
 * change screen management (i.e., dynamic array of lines)
diff --git a/draw.c b/draw.c
index 9724a1f..3c98534 100644
--- a/draw.c
+++ b/draw.c
@@ -299,9 +299,15 @@ void visual_box(FILE *fc){
 	draw_box(x,y,NOFIX);
 	while((c=fgetc(fc))!=EOF && c != 27 && c!= 'v' && c != '\n'){
 		if (!move_around(c, fc)) switch(c){
+			case 'y': /* yank (copy) */
+				yank_region(MIN(orig_x,x), MIN(orig_y,y), MAX(orig_x, x), MAX(orig_y, y));
+				goto vis_exit;
+				break;
 			case 'f':/* fill */
 				f = get_key(fc, "fill char: "); /** FALLTHROUGH **/
 			case 'x':/* erase */
+				if (c == 'x')
+					yank_region(MIN(orig_x,x), MIN(orig_y,y), MAX(orig_x, x), MAX(orig_y, y));
 				erase_box(orig_x, orig_y, f);
 				erase_blank_lines(MIN(y,orig_y), MAX(y, orig_y));
 				modified = 1;
@@ -323,3 +329,8 @@ vis_exit:
 	redraw();
 	mode = MOVE;
 }
+
+void paste(){
+	paste_region(x, y);
+	redraw();
+}
diff --git a/files.c b/files.c
index c007eac..245b29b 100644
--- a/files.c
+++ b/files.c
@@ -24,7 +24,7 @@ void write_file(FILE *fc){
 		return;
 	}
 	for (i=0; i<HEIGHT; i++){
-		fprintf(fout, "%s\n", screen[i].s);
+		fprintf(fout, "%s\n", screen.l[i].s);
 	}
 	fclose(fout);
 	modified = 0;
@@ -50,8 +50,8 @@ void load_file(FILE *fc){
 	get_string(fc, "Load file: ", newfname, 255);
 	if ((fin=fopen(newfname, "r")) != NULL){
 		i = 0;
-		while((fgets(screen[i].s, WIDTH+2, fin)) != NULL && i<HEIGHT)
-			screen[i++].s[WIDTH-1]='\0';
+		while((fgets(screen.l[i].s, WIDTH+2, fin)) != NULL && i<HEIGHT)
+			screen.l[i++].s[WIDTH-1]='\0';
 		for(;i<HEIGHT; i++){
 			erase_line(i);
 		}
diff --git a/gramscii.1 b/gramscii.1
index 10a1943..95b2719 100644
--- a/gramscii.1
+++ b/gramscii.1
@@ -57,6 +57,12 @@ Crop chart to the largest non-blank region. The first line and the first
 column of the cropped chart will contain the first non-blank line and
 the first non-blank column of the original chart, respectively.
 .TP 5m
+.BI p
+Paste the content of the yank buffer at the cursor position. The yank
+buffer contains the rectangle yanked/cut in
+.B visual
+mode. 
+.TP 5m
 .BI q
 Quit gramscii, and prompt for a filename if the current screen contains
 unsaved changes.
@@ -340,9 +346,19 @@ commands to highlight a rectangle. Then, you can use one of the
 following command on the highlighted region:
 .RS
 .TP 5m
+.BI y
+Yank (copy) the highlighted rectangle to the yank buffer. The content of
+the yank buffer can be retrieved by using the
+.B p
+command while in 
+.B move
+mode. The yank buffer is overwritten by subsequent yank/cut commands.
+.TP 5m
 .BI x
-Erase region. All the characters in the region are set to the default
-background character (space).
+Cut region. The content of the highlighted rectangle will be put in the
+yank buffer and all the characters in the region are set to the default
+background character (space). The yank buffer is overwritten by
+subsequent yank/cut commands.
 .TP 5m
 .BI f
 Fill region. gramscii will wait for a character on input and then will
diff --git a/gramscii.h b/gramscii.h
index b329d89..a08d211 100644
--- a/gramscii.h
+++ b/gramscii.h
@@ -5,13 +5,7 @@
 #include <termios.h>
 #include <unistd.h>
 
-/** types **/
 
-typedef struct{
-	int sz;
-	int lst;
-	char *s;
-} line_t;
 
 /** constants **/
 
@@ -50,6 +44,20 @@ typedef struct{
 #define VIDEO_NRM 0
 #define VIDEO_REV 7 
 
+/** types **/
+
+typedef struct{
+	int sz;/* allocated size*/
+	int n;/* line number */
+	int lst;/* last visible char (before the first \0) */
+	char *s;
+} line_t;
+
+typedef struct{
+	int sz;/* allocated size */
+	int num;/* number of lines stored */
+	line_t *l;
+} lineset_t;
 
 /** MACROS **/
 
@@ -63,8 +71,9 @@ typedef struct{
 
 /** global variables **/ 
 
-line_t *screen;
-int num_lines;
+lineset_t screen;
+lineset_t cutbuf;
+
 int WIDTH, HEIGHT;
 
 int mode;
@@ -96,6 +105,7 @@ char visual;
 char silent;
 char autoend;
 
+
 struct termios t1, t2, t3;
 
 /** screen-related functions **/
@@ -129,6 +139,7 @@ void get_box(FILE *fc);
 void get_arrow(FILE *fc);
 void erase(FILE *fc);
 void visual_box(FILE *fc);
+void paste();
 
 /** file-related functions **/
 void write_file(FILE *fc);
@@ -136,5 +147,14 @@ void check_modified(FILE *fc);
 void load_file(FILE *fc);
 void new_file(FILE *fc);
 
+/** line-related functions **/
+
+void dump_lines(lineset_t ls, FILE *f);
+void alloc_line(line_t *l);
+void ensure_line_length(line_t *l, int len);
+void ensure_num_lines(lineset_t *ls, int n);
+void yank_region(int x1, int y1, int x2, int y2);
+void paste_region(int x1, int y1);
+
 
 #endif
diff --git a/lineset.c b/lineset.c
new file mode 100644
index 0000000..faabb30
--- /dev/null
+++ b/lineset.c
@@ -0,0 +1,117 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "gramscii.h"
+
+static int LONG_STEP;
+
+/* line_t and lineset_t management */
+
+void ensure_line_length(line_t *l, int len){
+	char *tmp;
+
+	if (l->sz < len + 1){
+		tmp = realloc(l->s, (len+1) * 2 * sizeof(char));
+		if (!tmp){
+			fprintf(stderr, "Unable to allocate string\n");
+			exit(1);
+		}
+		l->s = tmp;
+		l->sz = (len + 1) * 2;
+	}
+}
+
+
+void alloc_line(line_t *l){
+	char *tmp;
+
+	l->sz = WIDTH+1;
+	tmp = malloc((l->sz) * sizeof(char));
+	if (tmp == NULL){
+		fprintf(stderr, "unable to allocate line\n");
+		exit(1);
+	}
+	l->s = tmp;
+	memset(l->s, BG, l->sz);
+	l->lst = -1;
+	l->s[0]='\0';
+}
+
+void ensure_num_lines(lineset_t *ls, int n){
+	line_t *tmp;
+
+	if (n > ls->sz){
+		if (ls->sz == 0)
+			ls->l=NULL;
+		tmp = realloc(ls->l, (n + LONG_STEP) * sizeof(line_t));
+		if (tmp == NULL){
+			fprintf(stderr, "Unable to allocate memory for more lines");
+			exit(1);
+		}
+		else {
+			ls->l = tmp;
+			while ( ls->sz < n + LONG_STEP){
+				alloc_line(&(ls->l[ls->sz]));
+				ls->sz ++;
+			}
+		}
+	}
+}
+
+void dump_lines(lineset_t ls, FILE *f){
+	int i;
+	for (i=0; i<ls.num ;i++){
+		fprintf(f, "%d:%s\n", i, ls.l[i].s);
+	}
+	fflush(f);
+}
+
+void pad_line_to_length(char *s, int W){
+
+	int i;
+
+	for (i=strlen(s); i<W; i++){
+		s[i] = BG;
+	}
+}
+
+/* cut/yank/paste/undo management */
+
+void yank_region(int x1, int y1, int x2, int y2){
+
+	int N, W, i;
+
+	N = y2 - y1 + 1;
+	W = x2 - x1 + 1;
+	ensure_num_lines(&cutbuf, N);
+	
+	for (i=y1; i<=y2; i++){
+		ensure_line_length(&(cutbuf.l[i-y1]), W);
+		memcpy(cutbuf.l[i-y1].s, screen.l[i].s + x1, x2-x1+1);
+		if (strlen(cutbuf.l[i-y1].s) < W)
+			pad_line_to_length(cutbuf.l[i-y1].s, W);
+		cutbuf.l[i-y1].s[W] = '\0';
+		cutbuf.l[i-y1].n = i;
+	}
+	cutbuf.num = N;
+#ifdef DEBUG
+	dump_lines(cutbuf, stderr);
+#endif
+	
+}
+
+
+void paste_region(int x1, int y1){
+	int i, curlen;
+
+	i = y1;
+	while( i < HEIGHT && i < y1 + cutbuf.num){
+		memcpy(screen.l[i].s + x1, cutbuf.l[i-y1].s, strlen(cutbuf.l[i-y1].s));
+		curlen = strlen(screen.l[i].s); 
+		if (curlen <= x1)
+			/* double-check this line below */
+			pad_line_to_length(screen.l[i].s+curlen, x1 - curlen);
+		i += 1;
+		modified = 1;
+	}
+}
diff --git a/main.c b/main.c
index 195d8af..d2d5a55 100644
--- a/main.c
+++ b/main.c
@@ -30,19 +30,13 @@
 
 char *argv0;
 
-void dump_lines(){
-	int i;
-	for (i=0; i<HEIGHT; i++){
-		printf("%s\n", screen[i].s);
-	}
-}
 
 void cleanup(int s){
 	
 	if (!silent)
 		printf("\033[;H\033[2J");
 	else
-		dump_lines();
+		dump_lines(screen, stdout);
 	tcsetattr(0, TCSANOW, &t1);
 	fflush(stdout);
 	exit(0);
@@ -125,6 +119,9 @@ void commands(FILE *fc){
 				case 'C':
 					crop_to_nonblank();
 					break;
+				case 'p':
+					paste();
+					break;
 				case 'q':
 					check_modified(fc);/** FALLTHROUGH **/
 				case 'Q':
diff --git a/screen.c b/screen.c
index 965d440..3cbfe12 100644
--- a/screen.c
+++ b/screen.c
@@ -1,9 +1,9 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <ctype.h>
 #include <termios.h>
 #include <sys/ioctl.h>
+#include <ctype.h>
 
 #include "gramscii.h"
 #include "config.h"
@@ -62,7 +62,7 @@ void status_bar(){
 	else
 		printf(" *%s*", fname );
 #ifdef DEBUG
-	printf("  '%d'  ", screen[y].s[x]);
+	printf("  '%d'  ", screen.l[y].s[x]);
 #endif
 	printf("\033[0m");
 	fflush(stdout);
@@ -109,52 +109,6 @@ int is_yes(char c){
 
 /*** Screen management ***/
 
-void ensure_line_length(int i, int len){
-	char *tmp;
-
-	if (screen[i].sz < len + 1){
-		tmp = realloc(screen[i].s, (len+1) * 2 * sizeof(char));
-		if (!tmp){
-			fprintf(stderr, "Unable to allocate string\n");
-			exit(1);
-		}
-		screen[i].s = tmp;
-		screen[i].sz = (len + 1) * 2;
-	}
-}
-
-
-void alloc_line(int i){
-	char *tmp;
-
-	screen[i].sz = WIDTH+1;
-	tmp = malloc((screen[i].sz) * sizeof(char));
-	if (tmp == NULL){
-		fprintf(stderr, "unable to allocate line %d\n", i+1);
-		exit(1);
-	}
-	screen[i].s = tmp;
-	memset(screen[i].s, BG, screen[i].sz);
-	screen[i].lst = -1;
-	screen[i].s[0]='\0';
-}
-
-void ensure_num_lines(int n){
-	line_t *tmp;
-
-	if (n > num_lines){
-		tmp = realloc(screen, (n + LONG_STEP) * sizeof(line_t));
-		if (tmp == NULL){
-			fprintf(stderr, "Unable to allocate memory for more lines");
-			exit(1);
-		}
-		else while ( num_lines < n + LONG_STEP){
-			alloc_line(num_lines);
-			num_lines ++;
-		}
-	}
-}
-
 
 void show_cursor(){
 	if (silent)
@@ -165,15 +119,15 @@ void show_cursor(){
 
 
 void set_xy(int _x, int _y, char c){
-	ensure_num_lines(_y + 1);
-	ensure_line_length(_y, _x + 1);
-	while (screen[_y].lst<_x){
-		screen[_y].lst ++;
-		screen[_y].s[screen[_y].lst] = BG;
+	ensure_num_lines(&screen, _y + 1);
+	ensure_line_length(&(screen.l[_y]), _x + 1);
+	while (screen.l[_y].lst<_x){
+		screen.l[_y].lst ++;
+		screen.l[_y].s[screen.l[_y].lst] = BG;
 	}
-	screen[_y].s[_x] = c;
-	if (_x == screen[_y].lst)
-		screen[_y].s[_x+1] = '\0';
+	screen.l[_y].s[_x] = c;
+	if (_x == screen.l[_y].lst)
+		screen.l[_y].s[_x+1] = '\0';
 }
 
 void set_cur(char c){
@@ -193,7 +147,7 @@ void update_current(){
 	if (silent)
 		return;
 	printf("\033[%d'%df",y+1,x+1);
-	putchar(screen[y].s[x]);
+	putchar(screen.l[y].s[x]);
 	fflush(stdout);
 }
 
@@ -206,20 +160,20 @@ void erase_blank_lines(int y1, int y2){
 	}
 	
 	for (; y1 <= y2; y1++){
-		j = screen[y1].lst; 
-		while (j>=0 && isblank(screen[y1].s[j]))
+		j = screen.l[y1].lst; 
+		while (j>=0 && isblank(screen.l[y1].s[j]))
 			j--;
 		if (j<0){
-			screen[y1].lst = -1;
-			screen[y1].s[0] = '\0';
+			screen.l[y1].lst = -1;
+			screen.l[y1].s[0] = '\0';
 		}
 	}
 }
 
 
 void erase_line(int i){
-	screen[i].lst = -1;
-	screen[i].s[0] = '\0';
+	screen.l[i].lst = -1;
+	screen.l[i].s[0] = '\0';
 }
 
 void erase_box(int x1, int y1, char c){
@@ -268,7 +222,7 @@ void redraw(){
 		return;
 	printf("\033[2J\033[1;1H");
 	for (i=0;i<HEIGHT;i++){
-		fprintf(stdout,"%s\n",screen[i].s);
+		fprintf(stdout,"%s\n",screen.l[i].s);
 	}
 	status_bar();
 	show_cursor();
@@ -435,14 +389,15 @@ void init_screen(){
 		WIDTH=80;
 		HEIGHT=24;
 	}
-	screen = malloc(HEIGHT * sizeof(line_t));
-	num_lines = HEIGHT;
-	if (screen == NULL){
+	screen.l = malloc(HEIGHT * sizeof(line_t));
+	screen.sz = HEIGHT;
+	screen.num = HEIGHT;
+	if (screen.l == NULL){
 		perror("allocating screen");
 		exit(1);
 	}
 	for (i=0; i<HEIGHT; i++){
-		alloc_line(i);
+		alloc_line(&(screen.l[i]));
 	}
 	hlines_sz= sizeof(hlines) -1;
 	vlines_sz= sizeof(vlines) -1;
@@ -450,6 +405,9 @@ void init_screen(){
 	stmarks_sz = sizeof(st_marks) - 1;
 	endmarks_sz = sizeof(st_marks) - 1;
 	reset_styles();
+	cutbuf.sz = 0;
+	cutbuf.l = NULL;
+	cutbuf.num = 0;
 }
 
 void find_nonblank_rect(int *x1, int *y1, int *x2, int *y2){
@@ -457,22 +415,22 @@ void find_nonblank_rect(int *x1, int *y1, int *x2, int *y2){
 	int i, j;
 	int first;
 	*x1= WIDTH; /** FIXME: replace with num_cols **/
-	*y1 = num_lines;
+	*y1 = screen.num;
 	*x2 = *y2 = 0; 
 
-	for (i=0; i<num_lines; i++){
-		if (screen[i].lst < 0)
+	for (i=0; i<screen.num; i++){
+		if (screen.l[i].lst < 0)
 			continue;
 		*y2 = i;
 		if (i < *y1)
 			*y1 = i;
 		j = 0;
-		while((j <= screen[i].lst)  && isblank(first=screen[i].s[j]))
+		while((j <= screen.l[i].lst)  && isblank(first=screen.l[i].s[j]))
 			j++;
 		if (j < *x1)
 			*x1 = j;
-		j = screen[i].lst;
-		while(isblank(screen[i].s[j]))
+		j = screen.l[i].lst;
+		while(isblank(screen.l[i].s[j]))
 			j--;
 		if (j > *x2)
 			*x2 = j;
@@ -483,13 +441,13 @@ void crop_to_rect(int x1, int y1, int x2, int y2){
 	int i;
 
 	for (i=0; i<= y2-y1; i ++){
-		ensure_line_length(i, screen[i+y1].lst);
-		sprintf(screen[i].s, "%s", screen[i+y1].s + x1);
-		screen[i].lst = screen[i+y1].lst - x1;
+		ensure_line_length(&(screen.l[i]), screen.l[i+y1].lst);
+		sprintf(screen.l[i].s, "%s", screen.l[i+y1].s + x1);
+		screen.l[i].lst = screen.l[i+y1].lst - x1;
 	} 
 	while (i<=y2){
-		screen[i].lst = -1;
-		screen[i].s[0]= '\0';
+		screen.l[i].lst = -1;
+		screen.l[i].s[0]= '\0';
 		i ++;
 	}
 }
@@ -505,3 +463,4 @@ void crop_to_nonblank(){
 	redraw();
 }
 
+
-- 
cgit v1.2.3