blob: d7898caab96ee5df2e5555cce18cbe2a7d693e82 [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;
"Vladimir N. Oleynik"8d3c40d2005-09-16 13:16:01 +0000673 regmatch_t match_structs;
674
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000675 memset(sub_line, 0, 256);
676 strcpy(line2, line);
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000677
678 match_found = 0;
679 match_status = regexec(pattern, line2, 1, &match_structs, 0);
680
681 while (match_status == 0) {
682
683 memset(sub_line, 0, 256);
684
685 if (match_found == 0)
686 match_found = 1;
687
688 line2 = insert_highlights(line2, match_structs.rm_so + prev_eo, match_structs.rm_eo + prev_eo);
689 if (match_structs.rm_eo + 11 + prev_eo < strlen(line2))
690 strcat(sub_line, line2 + match_structs.rm_eo + 11 + prev_eo);
691
692 prev_eo += match_structs.rm_eo + 11;
693 match_status = regexec(pattern, sub_line, 1, &match_structs, REG_NOTBOL);
694 }
695
696 return line2;
697}
698
699static void regex_process(void) {
700
701 char uncomp_regex[100];
702 char current_line[256];
703 int i;
704 int j = 0;
705 regex_t *pattern;
706
707 /* Reset variables */
708 match_lines[0] = -1;
709 match_pos = 0;
710 num_matches = 0;
711 match_found = 0;
712
713 pattern = (regex_t *) malloc(sizeof(regex_t));
714 memset(pattern, 0, sizeof(regex_t));
715
716 /* Get the uncompiled regular expression from the user */
717 clear_line();
718 if (match_backwards)
719 printf("?");
720 else
721 printf("/");
722 scanf("%s", uncomp_regex);
723
724 /* Compile the regex and check for errors */
725 xregcomp(pattern, uncomp_regex, 0);
726
727 /* Run the regex on each line of the current file here */
728 for (i = 0; i <= num_flines; i++) {
729 strcpy(current_line, process_regex_on_line(flines[i], pattern));
730 flines[i] = (char *) bb_xstrndup(current_line, sizeof(char) * (strlen(current_line)+1));
731
732 if (match_found) {
733 match_lines[j] = i;
734 j++;
735 }
736 }
737
738 num_matches = j;
739
740 if ((match_lines[0] != -1) && (num_flines > height - 2))
741 buffer_line(match_lines[0]);
742 else
743 buffer_init();
744}
745
746static void goto_match(int match) {
747
748 /* This goes to a specific match - all line positions of matches are
749 stored within the match_lines[] array. */
750 if ((match < num_matches) && (match >= 0)) {
751 buffer_line(match_lines[match]);
752 match_pos = match;
753 }
754}
755
756static void search_backwards(void) {
757
758 int current_linepos = line_pos;
759 int i;
760
761 match_backwards = 1;
762 regex_process();
763
764 for (i = 0; i < num_matches; i++) {
765 if (match_lines[i] > current_linepos) {
766 buffer_line(match_lines[i - num_back_match]);
767 break;
768 }
769 }
770
771 /* Reset variables */
772 match_backwards = 0;
773 num_back_match = 1;
774
775}
776#endif
777
778static void number_process(int first_digit) {
779
780 int i = 1;
781 int num;
782 char num_input[80];
783 char keypress;
784 num_input[0] = first_digit;
785
786 /* Clear the current line, print a prompt, and then print the digit */
787 clear_line();
788 printf(":%c", first_digit);
789
790 /* Receive input until a letter is given */
791 while((num_input[i] = tless_getch()) && isdigit(num_input[i])) {
792 printf("%c",num_input[i]);
793 i++;
794 }
795
796 /* Take the final letter out of the digits string */
797 keypress = num_input[i];
798 num_input[i] = '\0';
799 i--;
800 num = atoi(num_input);
801
802 /* We now know the number and the letter entered, so we process them */
803 switch (keypress) {
804 case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
805 buffer_down(num);
806 buffer_print();
807 break;
808 case KEY_UP: case 'b': case 'w': case 'y': case 'u':
809 buffer_up(num);
810 buffer_print();
811 break;
812 case 'g': case '<': case 'G': case '>':
813 if (num_flines >= height - 2)
814 buffer_line(num - 1);
815 buffer_print();
816 break;
817 case 'p': case '%':
818 buffer_line(reverse_percent(num));
819 buffer_print();
820 break;
821#ifdef CONFIG_FEATURE_LESS_REGEXP
822 case 'n':
823 goto_match(match_pos + num - 1);
824 buffer_print();
825 break;
826 case '/':
827 regex_process();
828 goto_match(num - 1);
829 buffer_print();
830 break;
831 case '?':
832 num_back_match = num;
833 search_backwards();
834 buffer_print();
835 break;
836#endif
837 default:
838 break;
839 }
840}
841
842#ifdef CONFIG_FEATURE_LESS_FLAGCS
843static void flag_change(void) {
844
845 int keypress;
846
847 clear_line();
848 printf("-");
849 keypress = tless_getch();
850
851 switch (keypress) {
852 case 'M':
853 M_FLAG = !M_FLAG;
854 break;
855 case 'm':
856 m_FLAG = !m_FLAG;
857 break;
858 case 'E':
859 E_FLAG = !E_FLAG;
860 break;
861 case '~':
862 TILDE_FLAG = !TILDE_FLAG;
863 break;
864 default:
865 break;
866 }
867}
868
869static void show_flag_status(void) {
870
871 int keypress;
872 int flag_val;
873
874 clear_line();
875 printf("_");
876 keypress = tless_getch();
877
878 switch (keypress) {
879 case 'M':
880 flag_val = M_FLAG;
881 break;
882 case 'm':
883 flag_val = m_FLAG;
884 break;
885 case '~':
886 flag_val = TILDE_FLAG;
887 break;
888 case 'N':
889 flag_val = N_FLAG;
890 break;
891 case 'E':
892 flag_val = E_FLAG;
893 break;
894 default:
895 flag_val = 0;
896 break;
897 }
898
899 clear_line();
900 printf("%s%s%i%s", HIGHLIGHT, "The status of the flag is: ", flag_val != 0, NORMAL);
901}
902#endif
903
904static void full_repaint(void) {
905
906 int temp_line_pos = line_pos;
907 data_readlines();
908 buffer_init();
909 buffer_line(temp_line_pos);
910 buffer_print();
911}
912
913
914static void save_input_to_file(void) {
915
916 char current_line[256];
917 int i;
918 FILE *fp;
919
920 clear_line();
921 printf("Log file: ");
922 fgets(current_line, 256, inp);
923 current_line[strlen(current_line) - 1] = '\0';
924 if (strlen(current_line)) {
925 fp = bb_xfopen(current_line, "w");
926 for (i = 0; i < num_flines; i++)
927 fprintf(fp, "%s", flines[i]);
928 fclose(fp);
929 buffer_print();
930 }
931 else
932 printf("%sNo log file%s", HIGHLIGHT, NORMAL);
933}
934
935#ifdef CONFIG_FEATURE_LESS_MARKS
936static void add_mark(void) {
937
938 int letter;
939 int mark_line;
940
941 clear_line();
942 printf("Mark: ");
943 letter = tless_getch();
944
945 if (isalpha(letter)) {
946 mark_line = line_pos;
947
948 /* If we exceed 15 marks, start overwriting previous ones */
949 if (num_marks == 14)
950 num_marks = 0;
951
952 mark_lines[num_marks][0] = letter;
953 mark_lines[num_marks][1] = line_pos;
954 num_marks++;
955 }
956 else {
957 clear_line();
958 printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL);
959 }
960}
961
962static void goto_mark(void) {
963
964 int letter;
965 int i;
966
967 clear_line();
968 printf("Go to mark: ");
969 letter = tless_getch();
970 if (isalpha(letter)) {
971 for (i = 0; i <= num_marks; i++)
972 if (letter == mark_lines[i][0]) {
973 buffer_line(mark_lines[i][1]);
974 break;
975 }
976 if ((num_marks == 14) && (letter != mark_lines[14][0])) {
977 clear_line();
978 printf("%s%s%s", HIGHLIGHT, "Mark not set", NORMAL);
979 }
980 }
981 else {
982 clear_line();
983 printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL);
984 }
985}
986#endif
987
988
989#ifdef CONFIG_FEATURE_LESS_BRACKETS
990
991static char opp_bracket (char bracket) {
992
993 switch (bracket) {
994 case '{': case '[':
995 return bracket + 2;
996 break;
997 case '(':
998 return ')';
999 break;
1000 case '}': case ']':
1001 return bracket - 2;
1002 break;
1003 case ')':
1004 return '(';
1005 break;
1006 default:
1007 return 0;
1008 break;
1009 }
1010}
1011
1012static void match_right_bracket(char bracket) {
1013
1014 int bracket_line = -1;
1015 int i;
1016
1017 if (strchr(flines[line_pos], bracket) == NULL) {
1018 clear_line();
1019 printf("%s%s%s", HIGHLIGHT, "No bracket in top line", NORMAL);
1020 }
1021 else {
1022 for (i = line_pos + 1; i < num_flines; i++) {
1023 if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
1024 bracket_line = i;
1025 break;
1026 }
1027 }
1028
1029 if (bracket_line == -1) {
1030 clear_line();
1031 printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL);
1032 }
1033
1034 buffer_line(bracket_line - height + 2);
1035 buffer_print();
1036 }
1037}
1038
1039static void match_left_bracket (char bracket) {
1040
1041 int bracket_line = -1;
1042 int i;
1043
1044 if (strchr(flines[line_pos + height - 2], bracket) == NULL) {
1045 clear_line();
1046 printf("%s%s%s", HIGHLIGHT, "No bracket in bottom line", NORMAL);
1047 printf("%s", flines[line_pos + height]);
1048 sleep(4);
1049 }
1050 else {
1051 for (i = line_pos + height - 2; i >= 0; i--) {
1052 if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
1053 bracket_line = i;
1054 break;
1055 }
1056 }
1057
1058 if (bracket_line == -1) {
1059 clear_line();
1060 printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL);
1061 }
1062
1063 buffer_line(bracket_line);
1064 buffer_print();
1065 }
1066}
1067
1068#endif /* CONFIG_FEATURE_LESS_BRACKETS */
1069
Rob Landley9200e792005-09-15 19:26:59 +00001070static void keypress_process(int keypress) {
1071 switch (keypress) {
1072 case KEY_DOWN: case 'e': case 'j': case '\015':
1073 buffer_down(1);
1074 buffer_print();
1075 break;
1076 case KEY_UP: case 'y': case 'k':
1077 buffer_up(1);
1078 buffer_print();
1079 break;
1080 case PAGE_DOWN: case ' ': case 'z':
1081 buffer_down(height - 1);
1082 buffer_print();
1083 break;
1084 case PAGE_UP: case 'w': case 'b':
1085 buffer_up(height - 1);
1086 buffer_print();
1087 break;
1088 case 'd':
1089 buffer_down((height - 1) / 2);
1090 buffer_print();
1091 break;
1092 case 'u':
1093 buffer_up((height - 1) / 2);
1094 buffer_print();
1095 break;
1096 case 'g': case 'p': case '<': case '%':
1097 buffer_up(num_flines + 1);
1098 buffer_print();
1099 break;
1100 case 'G': case '>':
1101 buffer_down(num_flines + 1);
1102 buffer_print();
1103 break;
1104 case 'q': case 'Q':
1105 tless_exit(0);
1106 break;
1107#ifdef CONFIG_FEATURE_LESS_MARKS
1108 case 'm':
1109 add_mark();
1110 buffer_print();
1111 break;
1112 case '\'':
1113 goto_mark();
1114 buffer_print();
1115 break;
1116#endif
1117 case 'r':
1118 buffer_print();
1119 break;
1120 case 'R':
1121 full_repaint();
1122 break;
1123 case 's':
1124 if (inp_stdin)
1125 save_input_to_file();
1126 break;
1127 case 'E':
1128 examine_file();
1129 break;
1130#ifdef CONFIG_FEATURE_LESS_FLAGS
1131 case '=':
1132 clear_line();
1133 m_status_print();
1134 break;
1135#endif
1136#ifdef CONFIG_FEATURE_LESS_REGEXP
1137 case '/':
1138 regex_process();
1139 buffer_print();
1140 break;
1141 case 'n':
1142 goto_match(match_pos + 1);
1143 buffer_print();
1144 break;
1145 case 'N':
1146 goto_match(match_pos - 1);
1147 buffer_print();
1148 break;
1149 case '?':
1150 search_backwards();
1151 buffer_print();
1152 break;
1153#endif
1154#ifdef CONFIG_FEATURE_LESS_FLAGCS
1155 case '-':
1156 flag_change();
1157 buffer_print();
1158 break;
1159 case '_':
1160 show_flag_status();
1161 break;
1162#endif
1163#ifdef CONFIG_FEATURE_LESS_BRACKETS
1164 case '{': case '(': case '[':
1165 match_right_bracket(keypress);
1166 break;
1167 case '}': case ')': case ']':
1168 match_left_bracket(keypress);
1169 break;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001170#endif
1171 case ':':
Rob Landley9200e792005-09-15 19:26:59 +00001172 colon_process();
1173 break;
1174 default:
1175 break;
1176 }
1177 if (isdigit(keypress))
1178 number_process(keypress);
1179}
1180
Rob Landley9200e792005-09-15 19:26:59 +00001181int less_main(int argc, char **argv) {
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001182
Rob Landley9200e792005-09-15 19:26:59 +00001183 unsigned long flags;
1184 int keypress;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001185
Rob Landley9200e792005-09-15 19:26:59 +00001186 flags = bb_getopt_ulflags(argc, argv, "EMNm~");
1187 E_FLAG = (flags & 1);
1188 M_FLAG = (flags & 2);
1189 N_FLAG = (flags & 4);
1190 m_FLAG = (flags & 8);
1191 TILDE_FLAG = (flags & 16);
1192
1193 argc -= optind;
1194 argv += optind;
1195 files = argv;
1196 num_files = argc;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001197
Rob Landley9200e792005-09-15 19:26:59 +00001198 if (!num_files) {
1199 if (ttyname(STDIN_FILENO) == NULL)
1200 inp_stdin = 1;
1201 else {
1202 bb_error_msg("Missing filename");
1203 bb_show_usage();
1204 }
1205 }
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001206
Rob Landley9200e792005-09-15 19:26:59 +00001207 strcpy(filename, (inp_stdin) ? "stdin" : files[0]);
1208 tty_width_height();
1209 data_readlines();
1210 buffer_init();
1211 buffer_print();
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001212
Rob Landley9200e792005-09-15 19:26:59 +00001213 while (1) {
1214 keypress = tless_getch();
1215 keypress_process(keypress);
1216 }
1217}