yank buffer and initial copy/cut/paste support - gramscii - A simple editor for… | |
Log | |
Files | |
Refs | |
Tags | |
README | |
LICENSE | |
--- | |
commit eebc645dee0d15871d6cc46f156d424cd916b191 | |
parent a99759398841d86928c7ad4d8248f907765cbeb2 | |
Author: KatolaZ <[email protected]> | |
Date: Tue, 30 Jul 2019 12:15:54 +0100 | |
yank buffer and initial copy/cut/paste support | |
Diffstat: | |
M Makefile | 2 +- | |
M TODO | 11 +++++++---- | |
M draw.c | 11 +++++++++++ | |
M files.c | 6 +++--- | |
M gramscii.1 | 20 ++++++++++++++++++-- | |
M gramscii.h | 36 ++++++++++++++++++++++++-----… | |
A lineset.c | 117 +++++++++++++++++++++++++++++… | |
M main.c | 11 ++++------- | |
M screen.c | 117 ++++++++++-------------------… | |
9 files changed, 227 insertions(+), 104 deletions(-) | |
--- | |
diff --git 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 | |
@@ -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 | |
@@ -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(… | |
+ goto vis_exit; | |
+ break; | |
case 'f':/* fill */ | |
f = get_key(fc, "fill char: "); /** FALLTHROUG… | |
case 'x':/* erase */ | |
+ if (c == 'x') | |
+ yank_region(MIN(orig_x,x), MIN(orig_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 | |
@@ -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 | |
@@ -57,6 +57,12 @@ Crop chart to the largest non-blank region. The first line a… | |
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 o… | |
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 | |
@@ -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 | |
@@ -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 li… | |
+ 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… | |
+ 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 | |
@@ -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 | |
@@ -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 li… | |
- 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 *y… | |
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(); | |
} | |
+ |