util-linux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Sami Kerola <kerolasa@iki.fi>
To: util-linux@vger.kernel.org
Cc: Sami Kerola <kerolasa@iki.fi>
Subject: [PATCH 2/2] more: make page and arrow up/down to update view
Date: Wed,  6 May 2020 20:19:23 +0100	[thread overview]
Message-ID: <20200506191923.5272-3-kerolasa@iki.fi> (raw)
In-Reply-To: <20200506191923.5272-1-kerolasa@iki.fi>

Aim was to introduce page and arrow up/down keys to more(1), but that
also required merging colon_command() and more_key_command() functions.

The more_key_commands enum is pointless from computers point of view.
The command identification performed in read_command() inline with
more_key_command() execution -- but that would be hard for humans, and
source code ought to serve both parties.

Reference: https://github.com/karelzak/util-linux/pull/1003
Signed-off-by: Sami Kerola <kerolasa@iki.fi>
---
 text-utils/more.c | 379 +++++++++++++++++++++++++++++-----------------
 1 file changed, 238 insertions(+), 141 deletions(-)

diff --git a/text-utils/more.c b/text-utils/more.c
index 014b40a6c..b69fa5c5b 100644
--- a/text-utils/more.c
+++ b/text-utils/more.c
@@ -93,6 +93,11 @@
 #define BACKSPACE	"\b"
 #define CARAT		"^"
 
+#define ARROW_UP	"\x1b\x5b\x41"
+#define ARROW_DOWN	"\x1b\x5b\x42"
+#define PAGE_UP		"\x1b\x5b\x35\x7e"
+#define PAGE_DOWN	"\x1b\x5b\x36\x7e"
+
 #define MIN_LINE_SZ	256	/* minimal line_buf buffer size */
 #define ESC		'\033'
 #define SCROLL_LEN	11
@@ -121,6 +126,35 @@
 #define TERM_STANDARD_MODE        "smso"
 #define TERM_STD_MODE_GLITCH      "xmc"
 
+/* Used in read_command() */
+typedef enum {
+	more_kc_unknown_command,
+	more_kc_colon,
+	more_kc_repeat_previous,
+	more_kc_backwards,
+	more_kc_jump_lines_per_screen,
+	more_kc_set_lines_per_screen,
+	more_kc_set_scroll_len,
+	more_kc_quit,
+	more_kc_skip_forward,
+	more_kc_next_line,
+	more_kc_clear_screen,
+	more_kc_previous_search_match,
+	more_kc_display_line,
+	more_kc_display_file_and_line,
+	more_kc_repeat_search,
+	more_kc_search,
+	more_kc_run_shell,
+	more_kc_help,
+	more_kc_next_file,
+	more_kc_previous_file,
+	more_kc_run_editor
+} more_key_commands;
+struct number_command {
+	unsigned int number;
+	more_key_commands key;
+};
+
 struct more_control {
 	struct termios output_tty;	/* output terminal */
 	struct termios original_tty;	/* original terminal settings */
@@ -157,9 +191,8 @@ struct more_control {
 		long line_num;		/* line number */
 	} context,
 	  screen_start;
-	cc_t last_key_command;		/* previous more key command */
-	int last_key_arg;		/* previous key command argument */
-	int last_colon_command;		/* is a colon-prefixed key command */
+	unsigned int leading_number;	/* number in front of key command */
+	struct number_command previous_command;	/* previous key command */
 	char *shell_line;		/* line to execute in subshell */
 #ifdef HAVE_MAGIC
 	magic_t magic;			/* libmagic database entries */
@@ -177,14 +210,15 @@ struct more_control {
 		fold_long_lines:1,	/* fold long lines */
 		hard_tabs:1,		/* print spaces instead of '\t' */
 		hard_tty:1,		/* is this hard copy terminal (a printer or such) */
+		leading_colon:1,	/* key command has leading ':' character */
 		is_paused:1,		/* is output paused */
 		no_quit_dialog:1,	/* suppress quit dialog */
 		no_scroll:1,		/* do not scroll, clear the screen and then display text */
 		no_tty_in:1,		/* is input in interactive mode */
 		no_tty_out:1,		/* is output in interactive mode */
 		print_banner:1,		/* print file name banner */
+		reading_num:1,		/* are we reading leading_number */
 		report_errors:1,	/* is an error reported */
-		run_previous_command:1,	/* run previous key command */
 		search_at_start:1,	/* search pattern defined at start up */
 		search_called:1,	/* previous more command was a search */
 		squeeze_spaces:1,	/* suppress white space */
@@ -736,6 +770,12 @@ static cc_t read_user_input(struct more_control *ctl)
 	cc_t c;
 
 	errno = 0;
+	/*
+	 * Key commands can be read() from either stderr or stdin.  If they
+	 * are read from stdin such as 'cat file.txt | more' then the pipe
+	 * input is understood as series key commands - and that is not
+	 * wanted.  Keep the read() reading from stderr.
+	 */
 	if (read(STDERR_FILENO, &c, 1) <= 0) {
 		if (errno != EINTR)
 			more_exit(ctl);
@@ -745,27 +785,124 @@ static cc_t read_user_input(struct more_control *ctl)
 	return c;
 }
 
-/* Read a decimal number from the terminal.  Set cmd to the non-digit
+/* Read a number and command from the terminal.  Set cmd to the non-digit
  * which terminates the number. */
-static int read_number(struct more_control *ctl, cc_t *cmd)
+static struct number_command read_command(struct more_control *ctl)
 {
-	int i;
-	cc_t ch;
-
-	i = 0;
-	ch = ctl->output_tty.c_cc[VKILL];
-	for (;;) {
-		ch = read_user_input(ctl);
-		if (isdigit(ch))
-			i = i * 10 + ch - '0';
-		else if (ch == ctl->output_tty.c_cc[VKILL])
-			i = 0;
-		else {
-			*cmd = ch;
+	cc_t input[8] = { 0 };
+	ssize_t i, ilen;
+	struct number_command cmd = { .key = more_kc_unknown_command };
+
+	/* See stderr note in read_user_input() */
+	if ((ilen = read(STDERR_FILENO, &input, sizeof(input))) <= 0)
+		return cmd;
+	if (2 < ilen) {
+		if (!memcmp(input, ARROW_UP, sizeof(ARROW_UP))) {
+			cmd.key = more_kc_backwards;
+			return cmd;
+		} else if (!memcmp(input, ARROW_DOWN, sizeof(ARROW_DOWN))) {
+			cmd.key = more_kc_skip_forward;
+			return cmd;
+		} else if (!memcmp(input, PAGE_UP, sizeof(PAGE_UP))) {
+			cmd.key = more_kc_backwards;
+			return cmd;
+		} else if (!memcmp(input, PAGE_DOWN, sizeof(PAGE_DOWN))) {
+			cmd.key = more_kc_skip_forward;
+			return cmd;
+		}
+	}
+	for (i = 0; i < ilen; i++) {
+		if (isdigit(input[i])) {
+			if (0 < ctl->reading_num) {
+				ctl->leading_number *= 10;
+				ctl->leading_number += input[i] - '0';
+			} else
+				ctl->leading_number = input[i] - '0';
+			ctl->reading_num = 1;
+			continue;
+		}
+		cmd.number = ctl->leading_number;
+		ctl->reading_num = 0;
+		ctl->leading_number = 0;
+		if (ctl->leading_colon) {
+			ctl->leading_colon = 0;
+			switch (input[i]) {
+			case 'f':
+				cmd.key = more_kc_display_file_and_line;
+				return cmd;
+			case 'n':
+				cmd.key = more_kc_next_file;
+				return cmd;
+			case 'p':
+				cmd.key = more_kc_previous_file;
+				return cmd;
+			default:
+				cmd.key = more_kc_unknown_command;
+				return cmd;
+			}
+		}
+		/* command is a single char */
+		switch (input[i]) {
+		case '.':
+			cmd.key = more_kc_repeat_previous;
+			break;
+		case ':':
+			ctl->leading_colon = 1;
+			break;
+		case 'b':
+		case CTRL('B'):
+			cmd.key = more_kc_backwards;
+			break;
+		case ' ':
+			cmd.key = more_kc_jump_lines_per_screen;
+			break;
+		case 'z':
+			cmd.key = more_kc_set_lines_per_screen;
+			break;
+		case 'd':
+		case CTRL('D'):
+			cmd.key = more_kc_set_scroll_len;
+			break;
+		case 'q':
+		case 'Q':
+			cmd.key = more_kc_quit;
+			break;
+		case 'f':
+		case 's':
+		case CTRL('F'):
+			cmd.key = more_kc_skip_forward;
+			break;
+		case '\n':
+			cmd.key = more_kc_next_line;
+			break;
+		case '\f':
+			cmd.key = more_kc_clear_screen;
+			break;
+		case '\'':
+			cmd.key = more_kc_previous_search_match;
+			break;
+		case '=':
+			cmd.key = more_kc_display_line;
+			break;
+		case 'n':
+			cmd.key = more_kc_repeat_search;
+			break;
+		case '/':
+			cmd.key = more_kc_search;
+			break;
+		case '!':
+			cmd.key = more_kc_run_shell;
+			break;
+		case '?':
+		case 'h':
+			cmd.key = more_kc_help;
+			break;
+		case 'v':
+			cmd.key = more_kc_run_editor;
 			break;
 		}
 	}
-	return i;
+	return cmd;
 }
 
 /* Change displayed file from command line list to next nskip, where nskip
@@ -1128,7 +1265,7 @@ static void run_shell(struct more_control *ctl, char *filename)
 	erase_to_col(ctl, 0);
 	putchar('!');
 	fflush(NULL);
-	if (ctl->run_previous_command && ctl->shell_line)
+	if (ctl->previous_command.key == more_kc_run_shell && ctl->shell_line)
 		fputs(ctl->shell_line, stdout);
 	else {
 		ttyin(ctl, cmdbuf, sizeof(cmdbuf) - 2, '!');
@@ -1145,60 +1282,6 @@ static void run_shell(struct more_control *ctl, char *filename)
 	execute(ctl, filename, ctl->shell, ctl->shell, "-c", ctl->shell_line, 0);
 }
 
-/* Execute a colon-prefixed command.  Returns <0 if not a command that
- * should cause more of the file to be printed. */
-static int colon_command(struct more_control *ctl, char *filename, int cmd, int nlines)
-{
-	char ch;
-
-	if (cmd == 0)
-		ch = read_user_input(ctl);
-	else
-		ch = cmd;
-	ctl->last_colon_command = ch;
-	switch (ch) {
-	case 'f':
-		erase_to_col(ctl, 0);
-		if (!ctl->no_tty_in)
-			ctl->prompt_len =
-			    printf(_("\"%s\" line %d"), ctl->file_names[ctl->argv_position], ctl->current_line);
-		else
-			ctl->prompt_len = printf(_("[Not a file] line %d"), ctl->current_line);
-		fflush(NULL);
-		return -1;
-	case 'n':
-		if (nlines == 0) {
-			if (ctl->argv_position >= ctl->num_files - 1)
-				more_exit(ctl);
-			nlines++;
-		}
-		putchar('\r');
-		erase_to_col(ctl, 0);
-		change_file(ctl, nlines);
-		return 0;
-	case 'p':
-		if (ctl->no_tty_in) {
-			fprintf(stderr, "\a");
-			return -1;
-		}
-		putchar('\r');
-		erase_to_col(ctl, 0);
-		if (nlines == 0)
-			nlines++;
-		change_file(ctl, -nlines);
-		return 0;
-	case '!':
-		run_shell(ctl, filename);
-		return -1;
-	case 'q':
-	case 'Q':
-		more_exit(ctl);
-	default:
-		fprintf(stderr, "\a");
-		return -1;
-	}
-}
-
 /* Skip n lines in the file f */
 static void skip_lines(struct more_control *ctl)
 {
@@ -1503,12 +1586,11 @@ static int skip_forwards(struct more_control *ctl, int nlines, cc_t comchar)
  * display in the current file, zero is returned. */
 static int more_key_command(struct more_control *ctl, char *filename)
 {
-	int nlines;
 	int retval = 0;
-	cc_t colonch;
-	int done = 0;
-	cc_t comchar;
+	int done = 0, search_again = 0;
 	char cmdbuf[INIT_BUF];
+	struct number_command cmd;
+
 	if (!ctl->report_errors)
 		output_prompt(ctl, filename);
 	else
@@ -1517,72 +1599,51 @@ static int more_key_command(struct more_control *ctl, char *filename)
 	for (;;) {
 		if (more_poll(ctl, -1) != 0)
 			continue;
-		nlines = read_number(ctl, &comchar);
-		ctl->run_previous_command = colonch = 0;
-		if (comchar == '.') {	/* Repeat last command */
-			ctl->run_previous_command++;
-			comchar = ctl->last_key_command;
-			nlines = ctl->last_key_arg;
-			if (ctl->last_key_command == ':')
-				colonch = ctl->last_colon_command;
-		}
-		ctl->last_key_command = comchar;
-		ctl->last_key_arg = nlines;
-		if (comchar == ctl->output_tty.c_cc[VERASE]) {
-			erase_to_col(ctl, 0);
-			output_prompt(ctl, filename);
+		cmd = read_command(ctl);
+		if (cmd.key == more_kc_unknown_command)
 			continue;
-		}
-		switch (comchar) {
-		case ':':
-			retval = colon_command(ctl, filename, colonch, nlines);
-			if (retval >= 0)
-				done++;
-			break;
-		case 'b':
-		case CTRL('B'):
+		if (cmd.key == more_kc_repeat_previous)
+			cmd = ctl->previous_command;
+		switch (cmd.key) {
+		case more_kc_backwards:
 			if (ctl->no_tty_in) {
 				fprintf(stderr, "\a");
 				return -1;
 			}
-			retval = skip_backwards(ctl, nlines);
+			retval = skip_backwards(ctl, cmd.number);
 			done = 1;
 			break;
-		case ' ':
-		case 'z':
-			if (nlines == 0)
-				nlines = ctl->lines_per_screen;
-			else if (comchar == 'z')
-				ctl->lines_per_screen = nlines;
-			retval = nlines;
+		case more_kc_jump_lines_per_screen:
+		case more_kc_set_lines_per_screen:
+			if (cmd.number == 0)
+				cmd.number = ctl->lines_per_screen;
+			else if (cmd.key == more_kc_set_lines_per_screen)
+				ctl->lines_per_screen = cmd.number;
+			retval = cmd.number;
 			done = 1;
 			break;
-		case 'd':
-		case CTRL('D'):
-			if (nlines != 0)
-				ctl->d_scroll_len = nlines;
+		case more_kc_set_scroll_len:
+			if (cmd.number != 0)
+				ctl->d_scroll_len = cmd.number;
 			retval = ctl->d_scroll_len;
 			done = 1;
 			break;
-		case 'q':
-		case 'Q':
+		case more_kc_quit:
 			more_exit(ctl);
-		case 's':
-		case 'f':
-		case CTRL('F'):
-			if (skip_forwards(ctl, nlines, comchar))
+		case more_kc_skip_forward:
+			if (skip_forwards(ctl, cmd.number, cmd.number))
 				retval = ctl->lines_per_screen;
 			done = 1;
 			break;
-		case '\n':
-			if (nlines != 0)
-				ctl->lines_per_screen = nlines;
+		case more_kc_next_line:
+			if (cmd.number != 0)
+				ctl->lines_per_screen = cmd.number;
 			else
-				nlines = 1;
-			retval = nlines;
+				cmd.number = 1;
+			retval = cmd.number;
 			done = 1;
 			break;
-		case '\f':
+		case more_kc_clear_screen:
 			if (!ctl->no_tty_in) {
 				more_clear_screen(ctl);
 				more_fseek(ctl, ctl->screen_start.row_num);
@@ -1594,7 +1655,7 @@ static int more_key_command(struct more_control *ctl, char *filename)
 				fprintf(stderr, "\a");
 				break;
 			}
-		case '\'':
+		case more_kc_previous_search_match:
 			if (!ctl->no_tty_in) {
 				erase_to_col(ctl, 0);
 				fputs(_("\n***Back***\n\n"), stdout);
@@ -1607,49 +1668,82 @@ static int more_key_command(struct more_control *ctl, char *filename)
 				fprintf(stderr, "\a");
 				break;
 			}
-		case '=':
+		case more_kc_display_line:
 			erase_to_col(ctl, 0);
 			ctl->prompt_len = printf("%d", ctl->current_line);
 			fflush(NULL);
 			break;
-		case 'n':
+		case more_kc_display_file_and_line:
+			erase_to_col(ctl, 0);
+			if (!ctl->no_tty_in)
+				ctl->prompt_len =
+				    printf(_("\"%s\" line %d"),
+				           ctl->file_names[ctl->argv_position], ctl->current_line);
+			else
+				ctl->prompt_len = printf(_("[Not a file] line %d"),
+							 ctl->current_line);
+			fflush(NULL);
+			break;
+		case more_kc_repeat_search:
 			if (!ctl->previous_search) {
 				more_error(ctl, _("No previous regular expression"));
 				break;
 			}
-			ctl->run_previous_command = 1;
+			search_again = 1;
 			/* fallthrough */
-		case '/':
-			if (nlines == 0)
-				nlines++;
+		case more_kc_search:
+			if (cmd.number == 0)
+				cmd.number++;
 			erase_to_col(ctl, 0);
 			putchar('/');
 			ctl->prompt_len = 1;
 			fflush(NULL);
-			if (ctl->run_previous_command) {
+			if (search_again) {
 				fputc('\r', stderr);
-				search(ctl, ctl->previous_search, nlines);
+				search(ctl, ctl->previous_search, cmd.number);
+				search_again = 0;
 			} else {
 				ttyin(ctl, cmdbuf, sizeof(cmdbuf) - 2, '/');
 				fputc('\r', stderr);
 				ctl->next_search = xstrdup(cmdbuf);
-				search(ctl, ctl->next_search, nlines);
+				search(ctl, ctl->next_search, cmd.number);
 			}
 			retval = ctl->lines_per_screen - 1;
 			done = 1;
 			break;
-		case '!':
+		case more_kc_run_shell:
 			run_shell(ctl, filename);
 			break;
-		case '?':
-		case 'h':
+		case more_kc_help:
 			if (ctl->no_scroll)
 				more_clear_screen(ctl);
 			erase_to_col(ctl, 0);
 			runtime_usage();
 			output_prompt(ctl, filename);
 			break;
-		case 'v':	/* This case should go right before default */
+		case more_kc_next_file:
+			putchar('\r');
+			erase_to_col(ctl, 0);
+			if (cmd.number == 0)
+				cmd.number = 1;
+			if (ctl->argv_position + cmd.number >= (unsigned int)ctl->num_files)
+				more_exit(ctl);
+			change_file(ctl, cmd.number);
+			done = 1;
+			break;
+		case more_kc_previous_file:
+			if (ctl->no_tty_in) {
+				fprintf(stderr, "\a");
+				break;
+			}
+			putchar('\r');
+			erase_to_col(ctl, 0);
+			if (cmd.number == 0)
+				cmd.number = 1;
+			change_file(ctl, -cmd.number);
+			done = 1;
+			break;
+		case more_kc_run_editor:	/* This case should go right before default */
 			if (!ctl->no_tty_in) {
 				execute_editor(ctl, cmdbuf, filename);
 				break;
@@ -1670,8 +1764,11 @@ static int more_key_command(struct more_control *ctl, char *filename)
 			fflush(NULL);
 			break;
 		}
-		if (done)
+		ctl->previous_command = cmd;
+		if (done) {
+			cmd.key = more_kc_unknown_command;
 			break;
+		}
 	}
 	putchar('\r');
 	ctl->no_quit_dialog = 1;
-- 
2.26.2


  parent reply	other threads:[~2020-05-06 19:19 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-05-06 19:19 [PATCH 0/2] more(1) use libmagic and make page up/down and arrow keys work Sami Kerola
2020-05-06 19:19 ` [PATCH 1/2] more: use libmagic to identify binary files Sami Kerola
2020-05-06 19:19 ` Sami Kerola [this message]
2020-05-12  8:53 ` [PATCH 0/2] more(1) use libmagic and make page up/down and arrow keys work Karel Zak
2020-05-12 13:45   ` Sami Kerola

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200506191923.5272-3-kerolasa@iki.fi \
    --to=kerolasa@iki.fi \
    --cc=util-linux@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).