blob: 99149a51d06faf8fc65336dc1d819716d51ef074 [file] [log] [blame]
Rob Landley9200e792005-09-15 19:26:59 +00001/* vi: set sw=4 ts=4: */
2/*
3 * Mini less implementation for busybox
4 *
5 *
6 * Copyright (C) 2005 by Rob Sullivan <cogito.ergo.cogito@gmail.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21 * 02111-1307 USA
22 *
23 * This program needs a lot of development, so consider it in a beta stage
24 * at best.
25 *
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +000026 * TODO:
Rob Landley9200e792005-09-15 19:26:59 +000027 * - Add more regular expression support - search modifiers, certain matches, etc.
28 * - Add more complex bracket searching - currently, nested brackets are
29 * not considered.
30 * - Add support for "F" as an input. This causes less to act in
31 * a similar way to tail -f.
32 * - Check for binary files, and prompt the user if a binary file
33 * is detected.
34 * - Allow horizontal scrolling. Currently, lines simply continue onto
35 * the next line, per the terminal's discretion
36 *
37 * Notes:
38 * - filename is an array and not a pointer because that avoids all sorts
39 * of complications involving the fact that something that is pointed to
40 * will be changed if the pointer is changed.
41 * - the inp file pointer is used so that keyboard input works after
42 * redirected input has been read from stdin
43*/
44
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <termios.h>
49#include <unistd.h>
Rob Landley9200e792005-09-15 19:26:59 +000050#include <ctype.h>
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +000051
Rob Landley9200e792005-09-15 19:26:59 +000052#include "busybox.h"
53
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +000054#ifdef CONFIG_FEATURE_LESS_REGEXP
55#include "xregex.h"
56#endif
57
58
Rob Landley9200e792005-09-15 19:26:59 +000059/* These are the escape sequences corresponding to special keys */
60#define REAL_KEY_UP 'A'
61#define REAL_KEY_DOWN 'B'
62#define REAL_KEY_RIGHT 'C'
63#define REAL_KEY_LEFT 'D'
64#define REAL_PAGE_UP '5'
65#define REAL_PAGE_DOWN '6'
66
67/* These are the special codes assigned by this program to the special keys */
68#define PAGE_UP 20
69#define PAGE_DOWN 21
70#define KEY_UP 22
71#define KEY_DOWN 23
72#define KEY_RIGHT 24
73#define KEY_LEFT 25
74
75/* The escape codes for highlighted and normal text */
76#define HIGHLIGHT "\033[7m"
77#define NORMAL "\033[0m"
78
79/* The escape code to clear the screen */
80#define CLEAR "\033[2J"
81
82/* Maximum number of lines in a file */
83#define MAXLINES 10000
84
85/* Get height and width of terminal */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +000086#define tty_width_height() get_terminal_width_height(0, &width, &height)
Rob Landley9200e792005-09-15 19:26:59 +000087
88static int height;
89static int width;
90static char **files;
91static char filename[256];
92static char buffer[100][256];
93static char *flines[MAXLINES];
94static int current_file = 1;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +000095static int line_pos;
Rob Landley9200e792005-09-15 19:26:59 +000096static int num_flines;
97static int num_files = 1;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +000098static int past_eof;
Rob Landley9200e792005-09-15 19:26:59 +000099
100/* Command line options */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000101static int E_FLAG;
102static int M_FLAG;
103static int N_FLAG;
104static int m_FLAG;
105static int TILDE_FLAG;
Rob Landley9200e792005-09-15 19:26:59 +0000106
107/* This is needed so that program behaviour changes when input comes from
108 stdin */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000109static int inp_stdin;
Rob Landley9200e792005-09-15 19:26:59 +0000110/* This is required so that when a file is requested to be examined after
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000111 input has come from stdin (e.g. dmesg | less), the input stream from
Rob Landley9200e792005-09-15 19:26:59 +0000112 the keyboard still stays the same. If it switched back to stdin, keyboard
113 input wouldn't work. */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000114static int ea_inp_stdin;
Rob Landley9200e792005-09-15 19:26:59 +0000115
116#ifdef CONFIG_FEATURE_LESS_MARKS
117static int mark_lines[15][2];
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000118static int num_marks;
Rob Landley9200e792005-09-15 19:26:59 +0000119#endif
120
121#ifdef CONFIG_FEATURE_LESS_REGEXP
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000122static int match_found;
Rob Landley9200e792005-09-15 19:26:59 +0000123static int match_lines[100];
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000124static int match_pos;
125static int num_matches;
126static int match_backwards;
Rob Landley9200e792005-09-15 19:26:59 +0000127static int num_back_match = 1;
128#endif
129
130/* Needed termios structures */
131static struct termios term_orig, term_vi;
132
133/* File pointer to get input from */
134static FILE *inp;
135
136/* Reset terminal input to normal */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000137static void set_tty_cooked(void) {
138 fflush(stdout);
139 tcsetattr(0, TCSANOW, &term_orig);
Rob Landley9200e792005-09-15 19:26:59 +0000140}
141
142/* Set terminal input to raw mode */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000143static void set_tty_raw(void) {
Rob Landley9200e792005-09-15 19:26:59 +0000144 tcgetattr(0, &term_orig);
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000145 term_vi = term_orig;
146 term_vi.c_lflag &= (~ICANON & ~ECHO);
147 term_vi.c_iflag &= (~IXON & ~ICRNL);
148 term_vi.c_oflag &= (~ONLCR);
149 term_vi.c_cc[VMIN] = 1;
150 term_vi.c_cc[VTIME] = 0;
151 tcsetattr(0, TCSANOW, &term_vi);
Rob Landley9200e792005-09-15 19:26:59 +0000152}
153
154/* Exit the program gracefully */
155static void tless_exit(int code) {
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000156
Rob Landley9200e792005-09-15 19:26:59 +0000157 /* TODO: We really should save the terminal state when we start,
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000158 and restore it when we exit. Less does this with the
Rob Landley9200e792005-09-15 19:26:59 +0000159 "ti" and "te" termcap commands; can this be done with
160 only termios.h? */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000161
Rob Landley9200e792005-09-15 19:26:59 +0000162 putchar('\n');
163 exit(code);
164}
165
166/* Grab a character from input without requiring the return key. If the
167 character is ASCII \033, get more characters and assign certain sequences
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000168 special return codes. Note that this function works best with raw input. */
169static int tless_getch(void) {
170
Rob Landley9200e792005-09-15 19:26:59 +0000171 set_tty_raw();
172 char input_key[3];
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000173
Rob Landley9200e792005-09-15 19:26:59 +0000174 input_key[0] = getc(inp);
175 /* Detect escape sequences (i.e. arrow keys) and handle
176 them accordingly */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000177
Rob Landley9200e792005-09-15 19:26:59 +0000178 if (input_key[0] == '\033') {
179 input_key[1] = getc(inp);
180 input_key[2] = getc(inp);
181 set_tty_cooked();
182 if (input_key[1] == '[') {
183 if (input_key[2] == REAL_KEY_UP)
184 return KEY_UP;
185 else if (input_key[2] == REAL_KEY_DOWN)
186 return KEY_DOWN;
187 else if (input_key[2] == REAL_KEY_RIGHT)
188 return KEY_RIGHT;
189 else if (input_key[2] == REAL_KEY_LEFT)
190 return KEY_LEFT;
191 else if (input_key[2] == REAL_PAGE_UP)
192 return PAGE_UP;
193 else if (input_key[2] == REAL_PAGE_DOWN)
194 return PAGE_DOWN;
195 }
196 }
197 /* The input is a normal ASCII value */
198 else {
199 set_tty_cooked();
200 return input_key[0];
201 }
202 return 0;
203}
204
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000205/* Move the cursor to a position (x,y), where (0,0) is the
Rob Landley9200e792005-09-15 19:26:59 +0000206 top-left corner of the console */
207static void move_cursor(int x, int y) {
208 printf("\033[%i;%iH", x, y);
209}
210
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000211static void clear_line(void) {
Rob Landley9200e792005-09-15 19:26:59 +0000212 move_cursor(height, 0);
213 printf("\033[K");
214}
215
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000216/* This adds line numbers to every line, as the -N flag necessitates */
217static void add_linenumbers(void) {
218
219 char current_line[256];
220 int i;
221
222 for (i = 0; i <= num_flines; i++) {
223 safe_strncpy(current_line, flines[i], 256);
224 flines[i] = xrealloc(flines[i], strlen(current_line) + 7 );
225 sprintf(flines[i],"%5d %s", i+1, current_line);
226 }
227}
228
229static void data_readlines(void) {
230
Rob Landley9200e792005-09-15 19:26:59 +0000231 int i;
232 char current_line[256];
233 FILE *fp;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000234
Rob Landley9200e792005-09-15 19:26:59 +0000235 fp = (inp_stdin) ? stdin : bb_xfopen(filename, "rt");
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000236
Rob Landley9200e792005-09-15 19:26:59 +0000237 for (i = 0; (!feof(fp)) && (i <= MAXLINES); i++) {
238 strcpy(current_line, "");
239 fgets(current_line, 256, fp);
240 bb_xferror(fp, filename);
241 flines[i] = (char *) bb_xstrndup(current_line, (strlen(current_line) + 1) * sizeof(char));
242 }
243 num_flines = i - 2;
244
245/* Reset variables for a new file */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000246
Rob Landley9200e792005-09-15 19:26:59 +0000247 line_pos = 0;
248 past_eof = 0;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000249
Rob Landley9200e792005-09-15 19:26:59 +0000250 fclose(fp);
251
252 if (inp_stdin)
253 inp = fopen(CURRENT_TTY, "r");
254 else
255 inp = stdin;
256
257 if (ea_inp_stdin) {
258 fclose(inp);
259 inp = fopen(CURRENT_TTY, "r");
260 }
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000261
Rob Landley9200e792005-09-15 19:26:59 +0000262 if (N_FLAG)
263 add_linenumbers();
264}
265
266/* Free the file data */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000267static void free_flines(void) {
268
Rob Landley9200e792005-09-15 19:26:59 +0000269 int i;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000270
Rob Landley9200e792005-09-15 19:26:59 +0000271 for (i = 0; i <= num_flines; i++)
272 free(flines[i]);
273}
274
275#ifdef CONFIG_FEATURE_LESS_FLAGS
276/* Calculate the percentage the current line position is through the file */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000277static int calc_percent(void) {
Rob Landley9200e792005-09-15 19:26:59 +0000278 return ((100 * (line_pos + height - 2) / num_flines) + 1);
279}
280#endif
281
282/* Turn a percentage into a line number */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000283static int reverse_percent(int percentage) {
Rob Landley9200e792005-09-15 19:26:59 +0000284 double linenum = percentage;
285 linenum = ((linenum / 100) * num_flines) - 1;
286 return(linenum);
287}
288
289#ifdef CONFIG_FEATURE_LESS_FLAGS
290/* Print a status line if -M was specified */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000291static void m_status_print(void) {
Rob Landley9200e792005-09-15 19:26:59 +0000292
293 int percentage;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000294
Rob Landley9200e792005-09-15 19:26:59 +0000295 if (!past_eof) {
296 if (!line_pos) {
297 if (num_files > 1)
298 printf("%s%s %s%i%s%i%s%i-%i/%i ", HIGHLIGHT, filename, "(file ", current_file, " of ", num_files, ") lines ", line_pos + 1, line_pos + height - 1, num_flines + 1);
299 else {
300 printf("%s%s lines %i-%i/%i ", HIGHLIGHT, filename, line_pos + 1, line_pos + height - 1, num_flines + 1);
301 }
302 }
303 else {
304 printf("%s %s lines %i-%i/%i ", HIGHLIGHT, filename, line_pos + 1, line_pos + height - 1, num_flines + 1);
305 }
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000306
Rob Landley9200e792005-09-15 19:26:59 +0000307 if (line_pos == num_flines - height + 2) {
308 printf("(END) %s", NORMAL);
309 if ((num_files > 1) && (current_file != num_files))
310 printf("%s- Next: %s%s", HIGHLIGHT, files[current_file], NORMAL);
311 }
312 else {
313 percentage = calc_percent();
314 printf("%i%s %s", percentage, "%", NORMAL);
315 }
316 }
317 else {
318 printf("%s%s lines %i-%i/%i (END) ", HIGHLIGHT, filename, line_pos + 1, num_flines + 1, num_flines + 1);
319 if ((num_files > 1) && (current_file != num_files))
320 printf("- Next: %s", files[current_file]);
321 printf("%s", NORMAL);
322 }
323}
324
325/* Print a status line if -m was specified */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000326static void medium_status_print(void) {
Rob Landley9200e792005-09-15 19:26:59 +0000327
328 int percentage;
329 percentage = calc_percent();
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000330
Rob Landley9200e792005-09-15 19:26:59 +0000331 if (!line_pos)
332 printf("%s%s %i%s%s", HIGHLIGHT, filename, percentage, "%", NORMAL);
333 else if (line_pos == num_flines - height + 2)
334 printf("%s(END)%s", HIGHLIGHT, NORMAL);
335 else
336 printf("%s%i%s%s", HIGHLIGHT, percentage, "%", NORMAL);
337}
338#endif
339
340/* Print the status line */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000341static void status_print(void) {
342
Rob Landley9200e792005-09-15 19:26:59 +0000343 /* Change the status if flags have been set */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000344#ifdef CONFIG_FEATURE_LESS_FLAGS
345 if (M_FLAG)
Rob Landley9200e792005-09-15 19:26:59 +0000346 m_status_print();
347 else if (m_FLAG)
348 medium_status_print();
349 /* No flags set */
350 else {
351#endif
352 if (!line_pos) {
353 printf("%s%s %s", HIGHLIGHT, filename, NORMAL);
354 if (num_files > 1)
355 printf("%s%s%i%s%i%s%s", HIGHLIGHT, "(file ", current_file, " of ", num_files, ")", NORMAL);
356 }
357 else if (line_pos == num_flines - height + 2) {
358 printf("%s%s %s", HIGHLIGHT, "(END)", NORMAL);
359 if ((num_files > 1) && (current_file != num_files))
360 printf("%s%s%s%s", HIGHLIGHT, "- Next: ", files[current_file], NORMAL);
361 }
362 else {
363 printf("%c", ':');
364 }
365#ifdef CONFIG_FEATURE_LESS_FLAGS
366 }
367#endif
368}
369
370/* Print the buffer */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000371static void buffer_print(void) {
372
Rob Landley9200e792005-09-15 19:26:59 +0000373 int i;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000374
Rob Landley9200e792005-09-15 19:26:59 +0000375 if (num_flines >= height - 2) {
376 printf("%s", CLEAR);
377 move_cursor(0,0);
378 for (i = 0; i < height - 1; i++)
379 printf("%s", buffer[i]);
380 status_print();
381 }
382 else {
383 printf("%s", CLEAR);
384 move_cursor(0,0);
385 for (i = 1; i < (height - 1 - num_flines); i++)
386 putchar('\n');
387 for (i = 0; i < height - 1; i++)
388 printf("%s", buffer[i]);
389 status_print();
390 }
391}
392
393/* Initialise the buffer */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000394static void buffer_init(void) {
395
Rob Landley9200e792005-09-15 19:26:59 +0000396 int i;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000397
Rob Landley9200e792005-09-15 19:26:59 +0000398 for (i = 0; i < (height - 1); i++)
399 memset(buffer[i], '\0', 256);
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000400
401 /* Fill the buffer until the end of the file or the
Rob Landley9200e792005-09-15 19:26:59 +0000402 end of the buffer is reached */
403 for (i = 0; (i < (height - 1)) && (i <= num_flines); i++) {
404 strcpy(buffer[i], flines[i]);
405 }
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000406
Rob Landley9200e792005-09-15 19:26:59 +0000407 /* If the buffer still isn't full, fill it with blank lines */
408 for (; i < (height - 1); i++) {
409 strcpy(buffer[i], "");
410 }
411}
412
413/* Move the buffer up and down in the file in order to scroll */
414static void buffer_down(int nlines) {
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000415
Rob Landley9200e792005-09-15 19:26:59 +0000416 int i;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000417
Rob Landley9200e792005-09-15 19:26:59 +0000418 if (!past_eof) {
419 if (line_pos + (height - 3) + nlines < num_flines) {
420 line_pos += nlines;
421 for (i = 0; i < (height - 1); i++)
422 strcpy(buffer[i], flines[line_pos + i]);
423 }
424 else {
425 /* As the number of lines requested was too large, we just move
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000426 to the end of the file */
427 while (line_pos + (height - 3) + 1 < num_flines) {
Rob Landley9200e792005-09-15 19:26:59 +0000428 line_pos += 1;
429 for (i = 0; i < (height - 1); i++)
430 strcpy(buffer[i], flines[line_pos + i]);
431 }
432 }
433
434 /* We exit if the -E flag has been set */
435 if (E_FLAG && (line_pos + (height - 2) == num_flines))
436 tless_exit(0);
437 }
438}
439
440static void buffer_up(int nlines) {
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000441
Rob Landley9200e792005-09-15 19:26:59 +0000442 int i;
443 int tilde_line;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000444
Rob Landley9200e792005-09-15 19:26:59 +0000445 if (!past_eof) {
446 if (line_pos - nlines >= 0) {
447 line_pos -= nlines;
448 for (i = 0; i < (height - 1); i++)
449 strcpy(buffer[i], flines[line_pos + i]);
450 }
451 else {
452 /* As the requested number of lines to move was too large, we
453 move one line up at a time until we can't. */
454 while (line_pos != 0) {
455 line_pos -= 1;
456 for (i = 0; i < (height - 1); i++)
457 strcpy(buffer[i], flines[line_pos + i]);
458 }
459 }
460 }
461 else {
462 /* Work out where the tildes start */
463 tilde_line = num_flines - line_pos + 3;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000464
Rob Landley9200e792005-09-15 19:26:59 +0000465 line_pos -= nlines;
466 /* Going backwards nlines lines has taken us to a point where
467 nothing is past the EOF, so we revert to normal. */
468 if (line_pos < num_flines - height + 3) {
469 past_eof = 0;
470 buffer_up(nlines);
471 }
472 else {
473 /* We only move part of the buffer, as the rest
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000474 is past the EOF */
Rob Landley9200e792005-09-15 19:26:59 +0000475 for (i = 0; i < (height - 1); i++) {
476 if (i < tilde_line - nlines + 1)
477 strcpy(buffer[i], flines[line_pos + i]);
478 else {
479 if (line_pos >= num_flines - height + 2)
480 strcpy(buffer[i], "~\n");
481 }
482 }
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000483 }
Rob Landley9200e792005-09-15 19:26:59 +0000484 }
485}
486
487static void buffer_line(int linenum) {
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000488
Rob Landley9200e792005-09-15 19:26:59 +0000489 int i;
490
491 past_eof = 0;
492
493 if (linenum < 1 || linenum > num_flines) {
494 clear_line();
495 printf("%s%s%i%s", HIGHLIGHT, "Cannot seek to line number ", linenum, NORMAL);
496 }
497 else if (linenum < (num_flines - height - 2)) {
498 for (i = 0; i < (height - 1); i++)
499 strcpy(buffer[i], flines[linenum + i]);
500 line_pos = linenum;
501 }
502 else {
503 for (i = 0; i < (height - 1); i++) {
504 if (linenum + i < num_flines + 2)
505 strcpy(buffer[i], flines[linenum + i]);
506 else
507 strcpy(buffer[i], (TILDE_FLAG) ? "\n" : "~\n");
508 }
509 line_pos = linenum;
510 /* Set past_eof so buffer_down and buffer_up act differently */
511 past_eof = 1;
512 }
513}
514
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000515static void examine_file(void) {
516
517 int newline_offset;
518
519 clear_line();
520 printf("Examine: ");
521 fgets(filename, 256, inp);
522
523 /* As fgets adds a newline to the end of an input string, we
524 need to remove it */
525 newline_offset = strlen(filename) - 1;
526 filename[newline_offset] = '\0';
527
528 files[num_files] = bb_xstrndup(filename, (strlen(filename) + 1) * sizeof(char));
529 current_file = num_files + 1;
530 num_files++;
531
532 inp_stdin = 0;
533 ea_inp_stdin = 1;
534 free_flines();
535 data_readlines();
536 buffer_init();
537 buffer_print();
538}
539
540
541static void next_file(void) {
542 if (current_file != num_files) {
543 current_file++;
544 strcpy(filename, files[current_file - 1]);
545 free_flines();
546 data_readlines();
547 buffer_init();
548 buffer_print();
549 }
550 else {
551 clear_line();
552 printf("%s%s%s", HIGHLIGHT, "No next file", NORMAL);
553 }
554}
555
556static void previous_file(void) {
557 if (current_file != 1) {
558 current_file--;
559 strcpy(filename, files[current_file - 1]);
560
561 free_flines();
562 data_readlines();
563 buffer_init();
564 buffer_print();
565 }
566 else {
567 clear_line();
568 printf("%s%s%s", HIGHLIGHT, "No previous file", NORMAL);
569 }
570}
571
572static void first_file(void) {
573 if (current_file != 1) {
574 current_file = 1;
575 strcpy(filename, files[current_file - 1]);
576 free_flines();
577 data_readlines();
578 buffer_init();
579 buffer_print();
580 }
581}
582
583static void remove_current_file(void) {
584
585 int i;
586
587 if (current_file != 1) {
588 previous_file();
589 for (i = 3; i <= num_files; i++)
590 files[i - 2] = files[i - 1];
591 num_files--;
592 buffer_print();
593 }
594 else {
595 next_file();
596 for (i = 2; i <= num_files; i++)
597 files[i - 2] = files[i - 1];
598 num_files--;
599 current_file--;
600 buffer_print();
601 }
602}
603
604static void colon_process(void) {
605
606 int keypress;
607
608 /* Clear the current line and print a prompt */
609 clear_line();
610 printf(" :");
611
612 keypress = tless_getch();
613 switch (keypress) {
614 case 'd':
615 remove_current_file();
616 break;
617 case 'e':
618 examine_file();
619 break;
620#ifdef CONFIG_FEATURE_LESS_FLAGS
621 case 'f':
622 clear_line();
623 m_status_print();
624 break;
625#endif
626 case 'n':
627 next_file();
628 break;
629 case 'p':
630 previous_file();
631 break;
632 case 'q':
633 tless_exit(0);
634 break;
635 case 'x':
636 first_file();
637 break;
638 default:
639 break;
640 }
641}
642
643#ifdef CONFIG_FEATURE_LESS_REGEXP
644/* The below two regular expression handler functions NEED development. */
645
646/* Get a regular expression from the user, and then go through the current
647 file line by line, running a processing regex function on each one. */
648
649static char *insert_highlights (char *line, int start, int end) {
650
651 char *new_line = (char *) malloc((sizeof(char) * (strlen(line) + 1)) + 10);
652
653 memset(new_line, 0, ((sizeof(char) * (strlen(line) + 1)) + 10));
654 strncat(new_line, line, start);
655 strcat(new_line, HIGHLIGHT);
656 strncat(new_line, line + start, end - start);
657 strcat(new_line, NORMAL);
658 strncat(new_line, line + end, strlen(line) - end);
659
660 return new_line;
661}
662
663static char *process_regex_on_line(char *line, regex_t *pattern) {
664 /* This function takes the regex and applies it to the line.
665 Each part of the line that matches has the HIGHLIGHT
666 and NORMAL escape sequences placed around it by
667 insert_highlights, and then the line is returned. */
668
669 int match_status;
670 char *line2 = (char *) malloc((sizeof(char) * (strlen(line) + 1)) + 64);
671 char sub_line[256];
672 int prev_eo = 0;
673 memset(sub_line, 0, 256);
674 strcpy(line2, line);
675 regmatch_t match_structs;
676
677 match_found = 0;
678 match_status = regexec(pattern, line2, 1, &match_structs, 0);
679
680 while (match_status == 0) {
681
682 memset(sub_line, 0, 256);
683
684 if (match_found == 0)
685 match_found = 1;
686
687 line2 = insert_highlights(line2, match_structs.rm_so + prev_eo, match_structs.rm_eo + prev_eo);
688 if (match_structs.rm_eo + 11 + prev_eo < strlen(line2))
689 strcat(sub_line, line2 + match_structs.rm_eo + 11 + prev_eo);
690
691 prev_eo += match_structs.rm_eo + 11;
692 match_status = regexec(pattern, sub_line, 1, &match_structs, REG_NOTBOL);
693 }
694
695 return line2;
696}
697
698static void regex_process(void) {
699
700 char uncomp_regex[100];
701 char current_line[256];
702 int i;
703 int j = 0;
704 regex_t *pattern;
705
706 /* Reset variables */
707 match_lines[0] = -1;
708 match_pos = 0;
709 num_matches = 0;
710 match_found = 0;
711
712 pattern = (regex_t *) malloc(sizeof(regex_t));
713 memset(pattern, 0, sizeof(regex_t));
714
715 /* Get the uncompiled regular expression from the user */
716 clear_line();
717 if (match_backwards)
718 printf("?");
719 else
720 printf("/");
721 scanf("%s", uncomp_regex);
722
723 /* Compile the regex and check for errors */
724 xregcomp(pattern, uncomp_regex, 0);
725
726 /* Run the regex on each line of the current file here */
727 for (i = 0; i <= num_flines; i++) {
728 strcpy(current_line, process_regex_on_line(flines[i], pattern));
729 flines[i] = (char *) bb_xstrndup(current_line, sizeof(char) * (strlen(current_line)+1));
730
731 if (match_found) {
732 match_lines[j] = i;
733 j++;
734 }
735 }
736
737 num_matches = j;
738
739 if ((match_lines[0] != -1) && (num_flines > height - 2))
740 buffer_line(match_lines[0]);
741 else
742 buffer_init();
743}
744
745static void goto_match(int match) {
746
747 /* This goes to a specific match - all line positions of matches are
748 stored within the match_lines[] array. */
749 if ((match < num_matches) && (match >= 0)) {
750 buffer_line(match_lines[match]);
751 match_pos = match;
752 }
753}
754
755static void search_backwards(void) {
756
757 int current_linepos = line_pos;
758 int i;
759
760 match_backwards = 1;
761 regex_process();
762
763 for (i = 0; i < num_matches; i++) {
764 if (match_lines[i] > current_linepos) {
765 buffer_line(match_lines[i - num_back_match]);
766 break;
767 }
768 }
769
770 /* Reset variables */
771 match_backwards = 0;
772 num_back_match = 1;
773
774}
775#endif
776
777static void number_process(int first_digit) {
778
779 int i = 1;
780 int num;
781 char num_input[80];
782 char keypress;
783 num_input[0] = first_digit;
784
785 /* Clear the current line, print a prompt, and then print the digit */
786 clear_line();
787 printf(":%c", first_digit);
788
789 /* Receive input until a letter is given */
790 while((num_input[i] = tless_getch()) && isdigit(num_input[i])) {
791 printf("%c",num_input[i]);
792 i++;
793 }
794
795 /* Take the final letter out of the digits string */
796 keypress = num_input[i];
797 num_input[i] = '\0';
798 i--;
799 num = atoi(num_input);
800
801 /* We now know the number and the letter entered, so we process them */
802 switch (keypress) {
803 case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
804 buffer_down(num);
805 buffer_print();
806 break;
807 case KEY_UP: case 'b': case 'w': case 'y': case 'u':
808 buffer_up(num);
809 buffer_print();
810 break;
811 case 'g': case '<': case 'G': case '>':
812 if (num_flines >= height - 2)
813 buffer_line(num - 1);
814 buffer_print();
815 break;
816 case 'p': case '%':
817 buffer_line(reverse_percent(num));
818 buffer_print();
819 break;
820#ifdef CONFIG_FEATURE_LESS_REGEXP
821 case 'n':
822 goto_match(match_pos + num - 1);
823 buffer_print();
824 break;
825 case '/':
826 regex_process();
827 goto_match(num - 1);
828 buffer_print();
829 break;
830 case '?':
831 num_back_match = num;
832 search_backwards();
833 buffer_print();
834 break;
835#endif
836 default:
837 break;
838 }
839}
840
841#ifdef CONFIG_FEATURE_LESS_FLAGCS
842static void flag_change(void) {
843
844 int keypress;
845
846 clear_line();
847 printf("-");
848 keypress = tless_getch();
849
850 switch (keypress) {
851 case 'M':
852 M_FLAG = !M_FLAG;
853 break;
854 case 'm':
855 m_FLAG = !m_FLAG;
856 break;
857 case 'E':
858 E_FLAG = !E_FLAG;
859 break;
860 case '~':
861 TILDE_FLAG = !TILDE_FLAG;
862 break;
863 default:
864 break;
865 }
866}
867
868static void show_flag_status(void) {
869
870 int keypress;
871 int flag_val;
872
873 clear_line();
874 printf("_");
875 keypress = tless_getch();
876
877 switch (keypress) {
878 case 'M':
879 flag_val = M_FLAG;
880 break;
881 case 'm':
882 flag_val = m_FLAG;
883 break;
884 case '~':
885 flag_val = TILDE_FLAG;
886 break;
887 case 'N':
888 flag_val = N_FLAG;
889 break;
890 case 'E':
891 flag_val = E_FLAG;
892 break;
893 default:
894 flag_val = 0;
895 break;
896 }
897
898 clear_line();
899 printf("%s%s%i%s", HIGHLIGHT, "The status of the flag is: ", flag_val != 0, NORMAL);
900}
901#endif
902
903static void full_repaint(void) {
904
905 int temp_line_pos = line_pos;
906 data_readlines();
907 buffer_init();
908 buffer_line(temp_line_pos);
909 buffer_print();
910}
911
912
913static void save_input_to_file(void) {
914
915 char current_line[256];
916 int i;
917 FILE *fp;
918
919 clear_line();
920 printf("Log file: ");
921 fgets(current_line, 256, inp);
922 current_line[strlen(current_line) - 1] = '\0';
923 if (strlen(current_line)) {
924 fp = bb_xfopen(current_line, "w");
925 for (i = 0; i < num_flines; i++)
926 fprintf(fp, "%s", flines[i]);
927 fclose(fp);
928 buffer_print();
929 }
930 else
931 printf("%sNo log file%s", HIGHLIGHT, NORMAL);
932}
933
934#ifdef CONFIG_FEATURE_LESS_MARKS
935static void add_mark(void) {
936
937 int letter;
938 int mark_line;
939
940 clear_line();
941 printf("Mark: ");
942 letter = tless_getch();
943
944 if (isalpha(letter)) {
945 mark_line = line_pos;
946
947 /* If we exceed 15 marks, start overwriting previous ones */
948 if (num_marks == 14)
949 num_marks = 0;
950
951 mark_lines[num_marks][0] = letter;
952 mark_lines[num_marks][1] = line_pos;
953 num_marks++;
954 }
955 else {
956 clear_line();
957 printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL);
958 }
959}
960
961static void goto_mark(void) {
962
963 int letter;
964 int i;
965
966 clear_line();
967 printf("Go to mark: ");
968 letter = tless_getch();
969 if (isalpha(letter)) {
970 for (i = 0; i <= num_marks; i++)
971 if (letter == mark_lines[i][0]) {
972 buffer_line(mark_lines[i][1]);
973 break;
974 }
975 if ((num_marks == 14) && (letter != mark_lines[14][0])) {
976 clear_line();
977 printf("%s%s%s", HIGHLIGHT, "Mark not set", NORMAL);
978 }
979 }
980 else {
981 clear_line();
982 printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL);
983 }
984}
985#endif
986
987
988#ifdef CONFIG_FEATURE_LESS_BRACKETS
989
990static char opp_bracket (char bracket) {
991
992 switch (bracket) {
993 case '{': case '[':
994 return bracket + 2;
995 break;
996 case '(':
997 return ')';
998 break;
999 case '}': case ']':
1000 return bracket - 2;
1001 break;
1002 case ')':
1003 return '(';
1004 break;
1005 default:
1006 return 0;
1007 break;
1008 }
1009}
1010
1011static void match_right_bracket(char bracket) {
1012
1013 int bracket_line = -1;
1014 int i;
1015
1016 if (strchr(flines[line_pos], bracket) == NULL) {
1017 clear_line();
1018 printf("%s%s%s", HIGHLIGHT, "No bracket in top line", NORMAL);
1019 }
1020 else {
1021 for (i = line_pos + 1; i < num_flines; i++) {
1022 if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
1023 bracket_line = i;
1024 break;
1025 }
1026 }
1027
1028 if (bracket_line == -1) {
1029 clear_line();
1030 printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL);
1031 }
1032
1033 buffer_line(bracket_line - height + 2);
1034 buffer_print();
1035 }
1036}
1037
1038static void match_left_bracket (char bracket) {
1039
1040 int bracket_line = -1;
1041 int i;
1042
1043 if (strchr(flines[line_pos + height - 2], bracket) == NULL) {
1044 clear_line();
1045 printf("%s%s%s", HIGHLIGHT, "No bracket in bottom line", NORMAL);
1046 printf("%s", flines[line_pos + height]);
1047 sleep(4);
1048 }
1049 else {
1050 for (i = line_pos + height - 2; i >= 0; i--) {
1051 if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
1052 bracket_line = i;
1053 break;
1054 }
1055 }
1056
1057 if (bracket_line == -1) {
1058 clear_line();
1059 printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL);
1060 }
1061
1062 buffer_line(bracket_line);
1063 buffer_print();
1064 }
1065}
1066
1067#endif /* CONFIG_FEATURE_LESS_BRACKETS */
1068
Rob Landley9200e792005-09-15 19:26:59 +00001069static void keypress_process(int keypress) {
1070 switch (keypress) {
1071 case KEY_DOWN: case 'e': case 'j': case '\015':
1072 buffer_down(1);
1073 buffer_print();
1074 break;
1075 case KEY_UP: case 'y': case 'k':
1076 buffer_up(1);
1077 buffer_print();
1078 break;
1079 case PAGE_DOWN: case ' ': case 'z':
1080 buffer_down(height - 1);
1081 buffer_print();
1082 break;
1083 case PAGE_UP: case 'w': case 'b':
1084 buffer_up(height - 1);
1085 buffer_print();
1086 break;
1087 case 'd':
1088 buffer_down((height - 1) / 2);
1089 buffer_print();
1090 break;
1091 case 'u':
1092 buffer_up((height - 1) / 2);
1093 buffer_print();
1094 break;
1095 case 'g': case 'p': case '<': case '%':
1096 buffer_up(num_flines + 1);
1097 buffer_print();
1098 break;
1099 case 'G': case '>':
1100 buffer_down(num_flines + 1);
1101 buffer_print();
1102 break;
1103 case 'q': case 'Q':
1104 tless_exit(0);
1105 break;
1106#ifdef CONFIG_FEATURE_LESS_MARKS
1107 case 'm':
1108 add_mark();
1109 buffer_print();
1110 break;
1111 case '\'':
1112 goto_mark();
1113 buffer_print();
1114 break;
1115#endif
1116 case 'r':
1117 buffer_print();
1118 break;
1119 case 'R':
1120 full_repaint();
1121 break;
1122 case 's':
1123 if (inp_stdin)
1124 save_input_to_file();
1125 break;
1126 case 'E':
1127 examine_file();
1128 break;
1129#ifdef CONFIG_FEATURE_LESS_FLAGS
1130 case '=':
1131 clear_line();
1132 m_status_print();
1133 break;
1134#endif
1135#ifdef CONFIG_FEATURE_LESS_REGEXP
1136 case '/':
1137 regex_process();
1138 buffer_print();
1139 break;
1140 case 'n':
1141 goto_match(match_pos + 1);
1142 buffer_print();
1143 break;
1144 case 'N':
1145 goto_match(match_pos - 1);
1146 buffer_print();
1147 break;
1148 case '?':
1149 search_backwards();
1150 buffer_print();
1151 break;
1152#endif
1153#ifdef CONFIG_FEATURE_LESS_FLAGCS
1154 case '-':
1155 flag_change();
1156 buffer_print();
1157 break;
1158 case '_':
1159 show_flag_status();
1160 break;
1161#endif
1162#ifdef CONFIG_FEATURE_LESS_BRACKETS
1163 case '{': case '(': case '[':
1164 match_right_bracket(keypress);
1165 break;
1166 case '}': case ')': case ']':
1167 match_left_bracket(keypress);
1168 break;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001169#endif
1170 case ':':
Rob Landley9200e792005-09-15 19:26:59 +00001171 colon_process();
1172 break;
1173 default:
1174 break;
1175 }
1176 if (isdigit(keypress))
1177 number_process(keypress);
1178}
1179
Rob Landley9200e792005-09-15 19:26:59 +00001180int less_main(int argc, char **argv) {
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001181
Rob Landley9200e792005-09-15 19:26:59 +00001182 unsigned long flags;
1183 int keypress;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001184
Rob Landley9200e792005-09-15 19:26:59 +00001185 flags = bb_getopt_ulflags(argc, argv, "EMNm~");
1186 E_FLAG = (flags & 1);
1187 M_FLAG = (flags & 2);
1188 N_FLAG = (flags & 4);
1189 m_FLAG = (flags & 8);
1190 TILDE_FLAG = (flags & 16);
1191
1192 argc -= optind;
1193 argv += optind;
1194 files = argv;
1195 num_files = argc;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001196
Rob Landley9200e792005-09-15 19:26:59 +00001197 if (!num_files) {
1198 if (ttyname(STDIN_FILENO) == NULL)
1199 inp_stdin = 1;
1200 else {
1201 bb_error_msg("Missing filename");
1202 bb_show_usage();
1203 }
1204 }
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001205
Rob Landley9200e792005-09-15 19:26:59 +00001206 strcpy(filename, (inp_stdin) ? "stdin" : files[0]);
1207 tty_width_height();
1208 data_readlines();
1209 buffer_init();
1210 buffer_print();
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001211
Rob Landley9200e792005-09-15 19:26:59 +00001212 while (1) {
1213 keypress = tless_getch();
1214 keypress_process(keypress);
1215 }
1216}