tabs.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. /****************************************************************************
  2. * Copyright (c) 2008 Free Software Foundation, Inc. *
  3. * *
  4. * Permission is hereby granted, free of charge, to any person obtaining a *
  5. * copy of this software and associated documentation files (the *
  6. * "Software"), to deal in the Software without restriction, including *
  7. * without limitation the rights to use, copy, modify, merge, publish, *
  8. * distribute, distribute with modifications, sublicense, and/or sell *
  9. * copies of the Software, and to permit persons to whom the Software is *
  10. * furnished to do so, subject to the following conditions: *
  11. * *
  12. * The above copyright notice and this permission notice shall be included *
  13. * in all copies or substantial portions of the Software. *
  14. * *
  15. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS *
  16. * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
  17. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
  18. * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
  19. * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
  20. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR *
  21. * THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
  22. * *
  23. * Except as contained in this notice, the name(s) of the above copyright *
  24. * holders shall not be used in advertising or otherwise to promote the *
  25. * sale, use or other dealings in this Software without prior written *
  26. * authorization. *
  27. ****************************************************************************/
  28. /****************************************************************************
  29. * Author: Thomas E. Dickey 2008 *
  30. ****************************************************************************/
  31. /*
  32. * tabs.c -- set terminal hard-tabstops
  33. */
  34. #define USE_LIBTINFO
  35. #include <progs.priv.h>
  36. MODULE_ID("$Id: tabs.c,v 1.15 2008/11/23 00:47:51 tom Exp $")
  37. static void usage(void) GCC_NORETURN;
  38. static int max_cols;
  39. static int
  40. putch(int c)
  41. {
  42. return putchar(c);
  43. }
  44. static void
  45. do_tabs(int *tab_list)
  46. {
  47. int last = 1;
  48. int stop;
  49. putchar('\r');
  50. while ((stop = *tab_list++) > 0) {
  51. if (last < stop) {
  52. while (last++ < stop) {
  53. if (last > max_cols)
  54. break;
  55. putchar(' ');
  56. }
  57. }
  58. if (stop <= max_cols) {
  59. tputs(tparm(set_tab, stop), 1, putch);
  60. last = stop;
  61. } else {
  62. break;
  63. }
  64. }
  65. putchar('\n');
  66. }
  67. static int *
  68. decode_tabs(const char *tab_list)
  69. {
  70. int *result = typeCalloc(int, strlen(tab_list) + (unsigned) max_cols);
  71. int n = 0;
  72. int value = 0;
  73. int prior = 0;
  74. int ch;
  75. if (result != 0) {
  76. while ((ch = *tab_list++) != '\0') {
  77. if (isdigit(UChar(ch))) {
  78. value *= 10;
  79. value += (ch - '0');
  80. } else if (ch == ',') {
  81. result[n] = value + prior;
  82. if (n > 0 && value <= result[n - 1]) {
  83. fprintf(stderr,
  84. "tab-stops are not in increasing order\n");
  85. free(result);
  86. result = 0;
  87. break;
  88. }
  89. ++n;
  90. value = 0;
  91. prior = 0;
  92. } else if (ch == '+') {
  93. if (n)
  94. prior = result[n - 1];
  95. }
  96. }
  97. }
  98. if (result != 0) {
  99. /*
  100. * If there is only one value, then it is an option such as "-8".
  101. */
  102. if ((n == 0) && (value > 0)) {
  103. int step = value;
  104. while (n < max_cols - 1) {
  105. result[n++] = value;
  106. value += step;
  107. }
  108. }
  109. /*
  110. * Add the last value, if any.
  111. */
  112. result[n++] = value;
  113. result[n] = 0;
  114. }
  115. return result;
  116. }
  117. static void
  118. print_ruler(int *tab_list)
  119. {
  120. int last = 0;
  121. int stop;
  122. int n;
  123. /* first print a readable ruler */
  124. for (n = 0; n < max_cols; n += 10) {
  125. int ch = 1 + (n / 10);
  126. char buffer[20];
  127. sprintf(buffer, "----+----%c",
  128. ((ch < 10)
  129. ? (ch + '0')
  130. : (ch + 'A' - 10)));
  131. printf("%.*s", ((max_cols - n) > 10) ? 10 : (max_cols - n), buffer);
  132. }
  133. putchar('\n');
  134. /* now, print '*' for each stop */
  135. for (n = 0, last = 0; (tab_list[n] > 0) && (last < max_cols); ++n) {
  136. stop = tab_list[n];
  137. while (++last < stop) {
  138. if (last <= max_cols) {
  139. putchar('-');
  140. } else {
  141. break;
  142. }
  143. }
  144. if (last <= max_cols) {
  145. putchar('*');
  146. last = stop;
  147. } else {
  148. break;
  149. }
  150. }
  151. while (++last <= max_cols)
  152. putchar('-');
  153. putchar('\n');
  154. }
  155. /*
  156. * Write an '*' on each tabstop, to demonstrate whether it lines up with the
  157. * ruler.
  158. */
  159. static void
  160. write_tabs(int *tab_list)
  161. {
  162. int stop;
  163. while ((stop = *tab_list++) > 0 && stop <= max_cols) {
  164. fputs((stop == 1) ? "*" : "\t*", stdout);
  165. };
  166. /* also show a tab _past_ the stops */
  167. if (stop < max_cols)
  168. fputs("\t+", stdout);
  169. putchar('\n');
  170. }
  171. /*
  172. * Trim leading/trailing blanks, as well as blanks after a comma.
  173. * Convert embedded blanks to commas.
  174. */
  175. static char *
  176. trimmed_tab_list(const char *source)
  177. {
  178. char *result = strdup(source);
  179. int ch, j, k, last;
  180. if (result != 0) {
  181. for (j = k = last = 0; result[j] != 0; ++j) {
  182. ch = UChar(result[j]);
  183. if (isspace(ch)) {
  184. if (last == '\0') {
  185. continue;
  186. } else if (isdigit(last) || last == ',') {
  187. ch = ',';
  188. }
  189. } else if (ch == ',') {
  190. ;
  191. } else {
  192. if (last == ',')
  193. result[k++] = last;
  194. result[k++] = ch;
  195. }
  196. last = ch;
  197. }
  198. result[k] = '\0';
  199. }
  200. return result;
  201. }
  202. static bool
  203. comma_is_needed(const char *source)
  204. {
  205. bool result = FALSE;
  206. if (source != 0) {
  207. unsigned len = strlen(source);
  208. if (len != 0)
  209. result = (source[len - 1] != ',');
  210. } else {
  211. result = FALSE;
  212. }
  213. return result;
  214. }
  215. /*
  216. * Add a command-line parameter to the tab-list. It can be blank- or comma-
  217. * separated (or a mixture). For simplicity, empty tabs are ignored, e.g.,
  218. * tabs 1,,6,11
  219. * tabs 1,6,11
  220. * are treated the same.
  221. */
  222. static const char *
  223. add_to_tab_list(char **append, const char *value)
  224. {
  225. char *result = *append;
  226. char *copied = trimmed_tab_list(value);
  227. if (copied != 0 && *copied != '\0') {
  228. const char *comma = ",";
  229. unsigned need = 1 + strlen(copied);
  230. if (*copied == ',')
  231. comma = "";
  232. else if (!comma_is_needed(*append))
  233. comma = "";
  234. need += strlen(comma);
  235. if (*append != 0)
  236. need += strlen(*append);
  237. result = malloc(need);
  238. if (result != 0) {
  239. *result = '\0';
  240. if (*append != 0) {
  241. strcpy(result, *append);
  242. free(*append);
  243. }
  244. strcat(result, comma);
  245. strcat(result, copied);
  246. }
  247. *append = result;
  248. }
  249. return result;
  250. }
  251. /*
  252. * Check for illegal characters in the tab-list.
  253. */
  254. static bool
  255. legal_tab_list(const char *program, const char *tab_list)
  256. {
  257. bool result = TRUE;
  258. if (tab_list != 0 && *tab_list != '\0') {
  259. if (comma_is_needed(tab_list)) {
  260. int n, ch;
  261. for (n = 0; tab_list[n] != '\0'; ++n) {
  262. ch = UChar(tab_list[n]);
  263. if (!(isdigit(ch) || ch == ',')) {
  264. fprintf(stderr,
  265. "%s: unexpected character found '%c'\n",
  266. program, ch);
  267. result = FALSE;
  268. break;
  269. }
  270. }
  271. } else {
  272. fprintf(stderr, "%s: trailing comma found '%s'\n", program, tab_list);
  273. result = FALSE;
  274. }
  275. } else {
  276. fprintf(stderr, "%s: no tab-list given\n", program);
  277. result = FALSE;
  278. }
  279. return result;
  280. }
  281. static void
  282. usage(void)
  283. {
  284. static const char *msg[] =
  285. {
  286. "Usage: tabs [options] [tabstop-list]"
  287. ,""
  288. ,"Options:"
  289. ," -0 reset tabs"
  290. ," -8 set tabs to standard interval"
  291. ," -a Assembler, IBM S/370, first format"
  292. ," -a2 Assembler, IBM S/370, second format"
  293. ," -c COBOL, normal format"
  294. ," -c2 COBOL compact format"
  295. ," -c3 COBOL compact format extended"
  296. ," -d debug (show ruler with expected/actual tab positions)"
  297. ," -f FORTRAN"
  298. ," -n no-op (do not modify terminal settings)"
  299. ," -p PL/I"
  300. ," -s SNOBOL"
  301. ," -u UNIVAC 1100 Assembler"
  302. ," -T name use terminal type 'name'"
  303. ,""
  304. ,"A tabstop-list is an ordered list of column numbers, e.g., 1,11,21"
  305. ,"or 1,+10,+10 which is the same."
  306. };
  307. unsigned n;
  308. fflush(stdout);
  309. for (n = 0; n < SIZEOF(msg); ++n) {
  310. fprintf(stderr, "%s\n", msg[n]);
  311. }
  312. ExitProgram(EXIT_FAILURE);
  313. }
  314. int
  315. main(int argc, char *argv[])
  316. {
  317. int rc = EXIT_FAILURE;
  318. bool debug = FALSE;
  319. bool no_op = FALSE;
  320. int n, ch;
  321. NCURSES_CONST char *term_name = 0;
  322. const char *mar_list = 0; /* ignored */
  323. char *append = 0;
  324. const char *tab_list = 0;
  325. if ((term_name = getenv("TERM")) == 0)
  326. term_name = "ansi+tabs";
  327. /* cannot use getopt, since some options are two-character */
  328. for (n = 1; n < argc; ++n) {
  329. char *option = argv[n];
  330. switch (option[0]) {
  331. case '-':
  332. while ((ch = *++option) != '\0') {
  333. switch (ch) {
  334. case 'a':
  335. switch (*option) {
  336. case '\0':
  337. tab_list = "1,10,16,36,72";
  338. /* Assembler, IBM S/370, first format */
  339. break;
  340. case '2':
  341. tab_list = "1,10,16,40,72";
  342. /* Assembler, IBM S/370, second format */
  343. break;
  344. default:
  345. usage();
  346. }
  347. break;
  348. case 'c':
  349. switch (*option) {
  350. case '\0':
  351. tab_list = "1,8,12,16,20,55";
  352. /* COBOL, normal format */
  353. break;
  354. case '2':
  355. tab_list = "1,6,10,14,49";
  356. /* COBOL compact format */
  357. break;
  358. case '3':
  359. tab_list = "1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67";
  360. /* COBOL compact format extended */
  361. break;
  362. default:
  363. usage();
  364. }
  365. break;
  366. case 'd': /* ncurses extension */
  367. debug = TRUE;
  368. break;
  369. case 'f':
  370. tab_list = "1,7,11,15,19,23";
  371. /* FORTRAN */
  372. break;
  373. case 'n': /* ncurses extension */
  374. no_op = TRUE;
  375. break;
  376. case 'p':
  377. tab_list = "1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61";
  378. /* PL/I */
  379. break;
  380. case 's':
  381. tab_list = "1,10,55";
  382. /* SNOBOL */
  383. break;
  384. case 'u':
  385. tab_list = "1,12,20,44";
  386. /* UNIVAC 1100 Assembler */
  387. break;
  388. case 'T':
  389. ++n;
  390. if (*++option != '\0') {
  391. term_name = option;
  392. } else {
  393. term_name = argv[n++];
  394. }
  395. option += ((int) strlen(option)) - 1;
  396. continue;
  397. default:
  398. if (isdigit(UChar(*option))) {
  399. tab_list = option;
  400. ++n;
  401. } else {
  402. usage();
  403. }
  404. option += ((int) strlen(option)) - 1;
  405. break;
  406. }
  407. }
  408. break;
  409. case '+':
  410. while ((ch = *++option) != '\0') {
  411. switch (ch) {
  412. case 'm':
  413. mar_list = option;
  414. break;
  415. default:
  416. usage();
  417. }
  418. }
  419. break;
  420. default:
  421. if (append != 0) {
  422. if (tab_list != (const char *) append) {
  423. /* one of the predefined options was used */
  424. free(append);
  425. append = 0;
  426. }
  427. }
  428. tab_list = add_to_tab_list(&append, option);
  429. option += ((int) strlen(option)) - 1;
  430. break;
  431. }
  432. }
  433. setupterm(term_name, STDOUT_FILENO, (int *) 0);
  434. max_cols = (columns > 0) ? columns : 80;
  435. if (!VALID_STRING(clear_all_tabs)) {
  436. fprintf(stderr,
  437. "%s: terminal type '%s' cannot reset tabs\n",
  438. argv[0], term_name);
  439. } else if (!VALID_STRING(set_tab)) {
  440. fprintf(stderr,
  441. "%s: terminal type '%s' cannot set tabs\n",
  442. argv[0], term_name);
  443. } else if (legal_tab_list(argv[0], tab_list)) {
  444. int *list = decode_tabs(tab_list);
  445. if (!no_op)
  446. tputs(clear_all_tabs, 1, putch);
  447. if (list != 0) {
  448. if (!no_op)
  449. do_tabs(list);
  450. if (debug) {
  451. fflush(stderr);
  452. printf("tabs %s\n", tab_list);
  453. print_ruler(list);
  454. write_tabs(list);
  455. }
  456. free(list);
  457. } else if (debug) {
  458. fflush(stderr);
  459. printf("tabs %s\n", tab_list);
  460. }
  461. rc = EXIT_SUCCESS;
  462. }
  463. if (append != 0)
  464. free(append);
  465. ExitProgram(rc);
  466. }