1 /*
2  * mc - columnate
3  *
4  * mc[-][-LINEWIDTH][-t][file...]
5  *	- causes break on colon
6  *	-LINEWIDTH sets width of line in which to columnate(default 80)
7  *	-t suppresses expanding multiple blanks into tabs
8  *
9  */
10 #include	<u.h>
11 #include	<sys/ioctl.h>
12 #include	<termios.h>
13 #ifdef HAS_SYS_TERMIOS
14 #endif
15 #include	<libc.h>
16 #include	<draw.h>
17 #include	<bio.h>
18 #include	<fcall.h>
19 #include	<9pclient.h>
20 #include	<thread.h>
21 
22 #define	WIDTH			80
23 #define	TAB	4
24 #define	WORD_ALLOC_QUANTA	1024
25 #define	ALLOC_QUANTA		4096
26 
27 int wordsize(Rune*, int);
28 int nexttab(int);
29 
30 int tabwid;
31 int mintab = 1;
32 int linewidth=WIDTH;
33 int colonflag=0;
34 int tabflag=0;	/* -t flag turned off forever, except in acme */
35 Rune *cbuf, *cbufp;
36 Rune **word;
37 int maxwidth=0;
38 int nalloc=ALLOC_QUANTA;
39 int nwalloc=WORD_ALLOC_QUANTA;
40 int nchars=0;
41 int nwords=0;
42 Biobuf	bin;
43 Biobuf	bout;
44 
45 void getwidth(void), readbuf(int), error(char *);
46 void scanwords(void), columnate(void), morechars(void);
47 
48 void
threadmain(int argc,char * argv[])49 threadmain(int argc, char *argv[])
50 {
51 	int i;
52 	int lineset;
53 	int ifd;
54 
55 	lineset = 0;
56 	Binit(&bout, 1, OWRITE);
57 	while(argc > 1 && argv[1][0] == '-'){
58 		--argc; argv++;
59 		switch(argv[0][1]){
60 		case '\0':
61 			colonflag = 1;
62 			break;
63 		case 't':
64 			tabflag = 0;
65 			break;
66 		default:
67 			linewidth = atoi(&argv[0][1]);
68 			if(linewidth <= 1)
69 				linewidth = WIDTH;
70 			lineset = 1;
71 			break;
72 		}
73 	}
74 	if(lineset == 0)
75 		getwidth();
76 	cbuf = cbufp = malloc(ALLOC_QUANTA*(sizeof *cbuf));
77 	word = malloc(WORD_ALLOC_QUANTA*(sizeof *word));
78 	if(word == 0 || cbuf == 0)
79 		error("out of memory");
80 	if(argc == 1)
81 		readbuf(0);
82 	else{
83 		for(i = 1; i < argc; i++){
84 			if((ifd = open(*++argv, OREAD)) == -1)
85 				fprint(2, "mc: can't open %s (%r)\n", *argv);
86 			else{
87 				readbuf(ifd);
88 				Bflush(&bin);
89 				close(ifd);
90 			}
91 		}
92 	}
93 	columnate();
94 	Bflush(&bout);
95 	threadexitsall(0);
96 }
97 void
error(char * s)98 error(char *s)
99 {
100 	fprint(2, "mc: %s\n", s);
101 	threadexitsall(s);
102 }
103 void
readbuf(int fd)104 readbuf(int fd)
105 {
106 	int lastwascolon = 0;
107 	long c;
108 	int linesiz = 0;
109 
110 	Binit(&bin, fd, OREAD);
111 	do{
112 		if(nchars++ >= nalloc)
113 			morechars();
114 		*cbufp++ = c = Bgetrune(&bin);
115 		linesiz++;
116 		if(c == '\t') {
117 			cbufp[-1] = L' ';
118 			while(linesiz%TAB != 0) {
119 				if(nchars++ >= nalloc)
120 					morechars();
121 				*cbufp++ = L' ';
122 				linesiz++;
123 			}
124 		}
125 		if(colonflag && c == ':')
126 			lastwascolon++;
127 		else if(lastwascolon){
128 			if(c == '\n'){
129 				--nchars; 	/* skip newline */
130 				*cbufp = L'\0';
131 				while(nchars > 0 && cbuf[--nchars] != '\n')
132 					;
133 				if(nchars)
134 					nchars++;
135 				columnate();
136 				if (nchars)
137 					Bputc(&bout, '\n');
138 				Bprint(&bout, "%S", cbuf+nchars);
139 				nchars = 0;
140 				cbufp = cbuf;
141 			}
142 			lastwascolon = 0;
143 		}
144 		if(c == '\n')
145 			linesiz = 0;
146 	}while(c >= 0);
147 }
148 void
scanwords(void)149 scanwords(void)
150 {
151 	Rune *p, *q;
152 	int i, w;
153 
154 	nwords=0;
155 	maxwidth=0;
156 	for(p = q = cbuf, i = 0; i < nchars; i++){
157 		if(*p++ == L'\n'){
158 			if(nwords >= nwalloc){
159 				nwalloc += WORD_ALLOC_QUANTA;
160 				if((word = realloc(word, nwalloc*sizeof(*word)))==0)
161 					error("out of memory");
162 			}
163 			word[nwords++] = q;
164 			p[-1] = L'\0';
165 			w = wordsize(q, p-q-1);
166 			if(w > maxwidth)
167 				maxwidth = w;
168 			q = p;
169 		}
170 	}
171 }
172 
173 void
columnate(void)174 columnate(void)
175 {
176 	int i, j;
177 	int words_per_line;
178 	int nlines;
179 	int col;
180 	int endcol;
181 
182 
183 	scanwords();
184 	if(nwords==0)
185 		return;
186 	maxwidth = nexttab(maxwidth+mintab-1);
187 	words_per_line = linewidth/maxwidth;
188 	if(words_per_line <= 0)
189 		words_per_line = 1;
190 	nlines=(nwords+words_per_line-1)/words_per_line;
191 	for(i = 0; i < nlines; i++){
192 		col = endcol = 0;
193 		for(j = i; j < nwords; j += nlines){
194 			endcol += maxwidth;
195 			Bprint(&bout, "%S", word[j]);
196 			col += wordsize(word[j], runestrlen(word[j]));
197 			if(j+nlines < nwords){
198 				if(tabflag) {
199 					while(col < endcol){
200 						Bputc(&bout, '\t');
201 						col = nexttab(col);
202 					}
203 				}else{
204 					while(col < endcol){
205 						Bputc(&bout, ' ');
206 						col++;
207 					}
208 				}
209 			}
210 		}
211 		Bputc(&bout, '\n');
212 	}
213 }
214 
215 int
wordsize(Rune * w,int nw)216 wordsize(Rune *w, int nw)
217 {
218 	if(nw < 0)
219 		abort();
220 	if(font)
221 		return runestringnwidth(font, w, nw);
222 	return nw;
223 }
224 
225 int
nexttab(int col)226 nexttab(int col)
227 {
228 	if(tabwid){
229 		col += tabwid;
230 		col -= col%tabwid;
231 		return col;
232 	}
233 	return col+1;
234 }
235 
236 void
morechars(void)237 morechars(void)
238 {
239 	nalloc += ALLOC_QUANTA;
240 	if((cbuf = realloc(cbuf, nalloc*sizeof(*cbuf))) == 0)
241 		error("out of memory");
242 	cbufp = cbuf+nchars-1;
243 }
244 
245 /*
246  * These routines discover the width of the display.
247  * It takes some work.  If we do the easy calls to the
248  * draw library, the screen flashes due to repainting
249  * when mc exits.
250  */
251 int
windowrect(struct winsize * ws)252 windowrect(struct winsize *ws)
253 {
254 	int tty;
255 
256 	if((tty = open("/dev/tty", OWRITE)) < 0)
257 		tty = 1;
258 
259 	if(ioctl(tty, TIOCGWINSZ, ws) < 0){
260 		if(tty != 1)
261 			close(tty);
262 		return -1;
263 	}
264 	if(tty != 1)
265 		close(tty);
266 	return 0;
267 }
268 
269 void
getwidth(void)270 getwidth(void)
271 {
272 	CFsys *fs;
273 	char buf[500], *p, *q, *f[10], *fname;
274 	int fd, n, nf, scale;
275 	struct winsize ws;
276 	Font *f1;
277 
278 	if((p = getenv("winid")) != nil){
279 		fs = nsmount("acme", "");
280 		if(fs == nil)
281 			return;
282 		snprint(buf, sizeof buf, "acme/%d/ctl", atoi(p));
283 		if((fd = fsopenfd(fs, buf, OREAD)) < 0)
284 			return;
285 		if((n=readn(fd, buf, sizeof buf-1)) <= 0)
286 			return;
287 		buf[n] = 0;
288 		if((nf=tokenize(buf, f, nelem(f))) < 7)
289 			return;
290 		// hidpi font in stringwidth(3) will call scalesubfont,
291 		// which aborts in bytesperline, due to unknow depth,
292 		// without initdraw.  We scale by ourselves.
293 		scale = parsefontscale(f[6], &fname);
294 		tabwid = 0;
295 		if(nf >= 8 && (tabwid = atoi(f[7])/scale) == 0)
296 			return;
297 		if((font = openfont(nil, fname)) == nil)
298 			return;
299 		mintab = stringwidth(font, "0");
300 		if(tabwid == 0)
301 			tabwid = mintab*4;
302 		linewidth = atoi(f[5]) / scale;
303 		tabflag = 1;
304 		return;
305 	}
306 
307 	if((p = getenv("termprog")) != nil && strcmp(p, "9term") == 0)
308 	if((p = getenv("font")) != nil)
309 		font = openfont(nil, p);
310 
311 	if(windowrect(&ws) < 0)
312 		return;
313 	if(ws.ws_xpixel == 0)
314 		font = nil;
315 	if(font){
316 		// 9term leaves "is this a hidpi display" in the low bit of the ypixel height.
317 		if(ws.ws_ypixel&1) {
318 			// need hidpi font.
319 			// loadhifpi creates a font that crashes in stringwidth,
320 			// for reasons i don't understand.
321 			// do it ourselves
322 			p = getenv("font");
323 			q = strchr(p, ',');
324 			f1 = nil;
325 			if(q != nil)
326 				f1 = openfont(nil, q+1);
327 			if(f1 != nil)
328 				font = f1;
329 			else
330 				ws.ws_xpixel /= 2;
331 		}
332 		mintab = stringwidth(font, "0");
333 		if((p = getenv("tabstop")) != nil)
334 			tabwid = atoi(p)*mintab;
335 		else
336 			tabwid = 4*mintab;
337 		tabflag = 1;
338 		linewidth = ws.ws_xpixel;
339 	}else
340 		linewidth = ws.ws_col;
341 }
342