xref: /openbsd/usr.bin/mg/yank.c (revision 5b133f3f)
1 /*	$OpenBSD: yank.c,v 1.16 2023/03/08 04:43:11 guenther Exp $	*/
2 
3 /* This file is in the public domain. */
4 
5 /*
6  *	kill ring functions
7  */
8 
9 #include <sys/queue.h>
10 #include <signal.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 
15 #include "def.h"
16 
17 #define KBLOCK	 8192		/* Kill grow.                    */
18 
19 static char	*kbufp = NULL;	/* Kill buffer data.		 */
20 static RSIZE	 kused = 0;	/* # of bytes used in KB.	 */
21 static RSIZE	 ksize = 0;	/* # of bytes allocated in KB.	 */
22 static RSIZE	 kstart = 0;	/* # of first used byte in KB.	 */
23 
24 static int	 kgrow(int);
25 
26 /*
27  * Delete all of the text saved in the kill buffer.  Called by commands when
28  * a new kill context is created. The kill buffer array is released, just in
29  * case the buffer has grown to an immense size.  No errors.
30  */
31 void
kdelete(void)32 kdelete(void)
33 {
34 	if (kbufp != NULL) {
35 		free(kbufp);
36 		kbufp = NULL;
37 		kstart = kused = ksize = 0;
38 	}
39 }
40 
41 /*
42  * Insert a character to the kill buffer, enlarging the buffer if there
43  * isn't any room. Always grow the buffer in chunks, on the assumption
44  * that if you put something in the kill buffer you are going to put more
45  * stuff there too later. Return TRUE if all is well, and FALSE on errors.
46  * Print a message on errors.  Dir says whether to put it at back or front.
47  * This call is ignored if  KNONE is set.
48  */
49 int
kinsert(int c,int dir)50 kinsert(int c, int dir)
51 {
52 	if (dir == KNONE)
53 		return (TRUE);
54 	if (kused == ksize && dir == KFORW && kgrow(dir) == FALSE)
55 		return (FALSE);
56 	if (kstart == 0 && dir == KBACK && kgrow(dir) == FALSE)
57 		return (FALSE);
58 	if (dir == KFORW)
59 		kbufp[kused++] = c;
60 	else if (dir == KBACK)
61 		kbufp[--kstart] = c;
62 	else
63 		panic("broken kinsert call");	/* Oh shit! */
64 	return (TRUE);
65 }
66 
67 /*
68  * kgrow - just get more kill buffer for the callee. If dir = KBACK
69  * we are trying to get space at the beginning of the kill buffer.
70  */
71 static int
kgrow(int dir)72 kgrow(int dir)
73 {
74 	int	 nstart;
75 	char	*nbufp;
76 
77 	if ((unsigned)(ksize + KBLOCK) <= (unsigned)ksize) {
78 		/* probably 16 bit unsigned */
79 		dobeep();
80 		ewprintf("Kill buffer size at maximum");
81 		return (FALSE);
82 	}
83 	if ((nbufp = malloc((unsigned)(ksize + KBLOCK))) == NULL) {
84 		dobeep();
85 		ewprintf("Can't get %ld bytes", (long)(ksize + KBLOCK));
86 		return (FALSE);
87 	}
88 	nstart = (dir == KBACK) ? (kstart + KBLOCK) : (KBLOCK / 4);
89 	bcopy(&(kbufp[kstart]), &(nbufp[nstart]), (int)(kused - kstart));
90 	free(kbufp);
91 	kbufp = nbufp;
92 	ksize += KBLOCK;
93 	kused = kused - kstart + nstart;
94 	kstart = nstart;
95 	return (TRUE);
96 }
97 
98 /*
99  * This function gets characters from the kill buffer. If the character
100  * index "n" is off the end, it returns "-1". This lets the caller just
101  * scan along until it gets a "-1" back.
102  */
103 int
kremove(int n)104 kremove(int n)
105 {
106 	if (n < 0 || n + kstart >= kused)
107 		return (-1);
108 	return (CHARMASK(kbufp[n + kstart]));
109 }
110 
111 /*
112  * Copy a string into the kill buffer. kflag gives direction.
113  * if KNONE, do nothing.
114  */
115 int
kchunk(char * cp1,RSIZE chunk,int kflag)116 kchunk(char *cp1, RSIZE chunk, int kflag)
117 {
118 	/*
119 	 * HACK - doesn't matter, and fixes back-over-nl bug for empty
120 	 *	kill buffers.
121 	 */
122 	if (kused == kstart)
123 		kflag = KFORW;
124 
125 	if (kflag & KFORW) {
126 		while (ksize - kused < chunk)
127 			if (kgrow(kflag) == FALSE)
128 				return (FALSE);
129 		bcopy(cp1, &(kbufp[kused]), (int)chunk);
130 		kused += chunk;
131 	} else if (kflag & KBACK) {
132 		while (kstart < chunk)
133 			if (kgrow(kflag) == FALSE)
134 				return (FALSE);
135 		bcopy(cp1, &(kbufp[kstart - chunk]), (int)chunk);
136 		kstart -= chunk;
137 	}
138 
139 	return (TRUE);
140 }
141 
142 /*
143  * Kill line.  If called without an argument, it kills from dot to the end
144  * of the line, unless it is at the end of the line, when it kills the
145  * newline.  If called with an argument of 0, it kills from the start of the
146  * line to dot.  If called with a positive argument, it kills from dot
147  * forward over that number of newlines.  If called with a negative argument
148  * it kills any text before dot on the current line, then it kills back
149  * abs(arg) lines.
150  */
151 int
killline(int f,int n)152 killline(int f, int n)
153 {
154 	struct line	*nextp;
155 	RSIZE	 chunk;
156 	int	 i, c;
157 
158 	/* clear kill buffer if last wasn't a kill */
159 	if ((lastflag & CFKILL) == 0)
160 		kdelete();
161 	thisflag |= CFKILL;
162 	if (!(f & FFARG)) {
163 		for (i = curwp->w_doto; i < llength(curwp->w_dotp); ++i)
164 			if ((c = lgetc(curwp->w_dotp, i)) != ' ' && c != '\t')
165 				break;
166 		if (i == llength(curwp->w_dotp))
167 			chunk = llength(curwp->w_dotp) - curwp->w_doto + 1;
168 		else {
169 			chunk = llength(curwp->w_dotp) - curwp->w_doto;
170 			if (chunk == 0)
171 				chunk = 1;
172 		}
173 	} else if (n > 0) {
174 		chunk = llength(curwp->w_dotp) - curwp->w_doto;
175 		nextp = lforw(curwp->w_dotp);
176 		if (nextp != curbp->b_headp)
177 			chunk++;		/* newline */
178 		if (nextp == curbp->b_headp)
179 			goto done;		/* EOL */
180 		i = n;
181 		while (--i) {
182 			chunk += llength(nextp);
183 			nextp = lforw(nextp);
184 			if (nextp != curbp->b_headp)
185 				chunk++;	/* newline */
186 			if (nextp == curbp->b_headp)
187 				break;		/* EOL */
188 		}
189 	} else {
190 		/* n <= 0 */
191 		chunk = curwp->w_doto;
192 		curwp->w_doto = 0;
193 		i = n;
194 		while (i++) {
195 			if (lforw(curwp->w_dotp))
196 				chunk++;
197 			curwp->w_dotp = lback(curwp->w_dotp);
198 			curwp->w_rflag |= WFMOVE;
199 			chunk += llength(curwp->w_dotp);
200 		}
201 	}
202 	/*
203 	 * KFORW here is a bug.  Should be KBACK/KFORW, but we need to
204 	 * rewrite the ldelete code (later)?
205 	 */
206 done:
207 	if (chunk)
208 		return (ldelete(chunk, KFORW));
209 	return (TRUE);
210 }
211 
212 /*
213  * Yank text back from the kill buffer.  This is really easy.  All of the work
214  * is done by the standard insert routines.  All you do is run the loop, and
215  * check for errors.  The blank lines are inserted with a call to "newline"
216  * instead of a call to "lnewline" so that the magic stuff that happens when
217  * you type a carriage return also happens when a carriage return is yanked
218  * back from the kill buffer.  An attempt has been made to fix the cosmetic
219  * bug associated with a yank when dot is on the top line of the window
220  * (nothing moves, because all of the new text landed off screen).
221  */
222 int
yank(int f,int n)223 yank(int f, int n)
224 {
225 	struct line	*lp;
226 	int	 c, i, nline;
227 
228 	if (n < 0)
229 		return (FALSE);
230 
231 	/* newline counting */
232 	nline = 0;
233 
234 	undo_boundary_enable(FFRAND, 0);
235 	while (n--) {
236 		/* mark around last yank */
237 		isetmark();
238 		i = 0;
239 		while ((c = kremove(i)) >= 0) {
240 			if (c == *curbp->b_nlchr) {
241 				if (enewline(FFRAND, 1) == FALSE)
242 					return (FALSE);
243 				++nline;
244 			} else {
245 				if (linsert(1, c) == FALSE)
246 					return (FALSE);
247 			}
248 			++i;
249 		}
250 	}
251 	/* cosmetic adjustment */
252 	lp = curwp->w_linep;
253 
254 	/* if offscreen insert */
255 	if (curwp->w_dotp == lp) {
256 		while (nline-- && lback(lp) != curbp->b_headp)
257 			lp = lback(lp);
258 		/* adjust framing */
259 		curwp->w_linep = lp;
260 		curwp->w_rflag |= WFFULL;
261 	}
262 	undo_boundary_enable(FFRAND, 1);
263 	return (TRUE);
264 }
265 
266