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