diff --git a/builtin.cpp b/builtin.cpp
index da9429b17..cde4477fb 100644
--- a/builtin.cpp
+++ b/builtin.cpp
@@ -2313,6 +2313,8 @@ static int builtin_read(parser_t &parser, wchar_t **argv)
const wchar_t *commandline = L"";
int exit_res=STATUS_BUILTIN_OK;
const wchar_t *mode_name = READ_MODE_NAME;
+ int nchars=0;
+ wchar_t *end;
int shell = 0;
int array = 0;
@@ -2355,6 +2357,10 @@ static int builtin_read(parser_t &parser, wchar_t **argv)
L"mode-name", required_argument, 0, 'm'
}
,
+ {
+ L"nchars", required_argument, 0, 'n'
+ }
+ ,
{
L"shell", no_argument, 0, 's'
}
@@ -2377,7 +2383,7 @@ static int builtin_read(parser_t &parser, wchar_t **argv)
int opt = wgetopt_long(argc,
argv,
- L"xglUup:c:hm:sa",
+ L"xglUup:c:hm:n:sa",
long_options,
&opt_index);
if (opt == -1)
@@ -2428,6 +2434,32 @@ static int builtin_read(parser_t &parser, wchar_t **argv)
mode_name = woptarg;
break;
+ case L'n':
+ errno = 0;
+ nchars = fish_wcstoi(woptarg, &end, 10);
+ if (errno || *end != 0)
+ {
+ switch (errno)
+ {
+ case ERANGE:
+ append_format(stderr_buffer,
+ _(L"%ls: Argument '%ls' is out of range\n"),
+ argv[0],
+ woptarg);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+
+ default:
+ append_format(stderr_buffer,
+ _(L"%ls: Argument '%ls' must be an integer\n"),
+ argv[0],
+ woptarg);
+ builtin_print_help(parser, argv[0], stderr_buffer);
+ return STATUS_BUILTIN_ERROR;
+ }
+ }
+ break;
+
case 's':
shell = 1;
break;
@@ -2530,11 +2562,24 @@ static int builtin_read(parser_t &parser, wchar_t **argv)
proc_push_interactive(1);
event_fire_generic(L"fish_prompt");
- line = reader_readline();
+ line = reader_readline(nchars);
proc_pop_interactive();
if (line)
{
- buff = wcsdup(line);
+ if (0 < nchars && nchars < wcslen(line))
+ {
+ // line may be longer than nchars if a keybinding used `commandline -i`
+ // note: we're deliberately throwing away the tail of the commandline.
+ // It shouldn't be unread because it was produced with `commandline -i`,
+ // not typed.
+ buff = (wchar_t *)malloc(((size_t)nchars + 1) * sizeof(wchar_t));
+ wmemcpy(buff, line, (size_t)nchars);
+ buff[nchars] = 0;
+ }
+ else
+ {
+ buff = wcsdup(line);
+ }
}
else
{
@@ -2594,6 +2639,11 @@ static int builtin_read(parser_t &parser, wchar_t **argv)
break;
sb.push_back(res);
+
+ if (0 < nchars && (size_t)nchars <= sb.size())
+ {
+ break;
+ }
}
if (sb.size() < 2 && eof)
diff --git a/doc_src/read.txt b/doc_src/read.txt
index 4f1760a56..48ae9b059 100644
--- a/doc_src/read.txt
+++ b/doc_src/read.txt
@@ -14,6 +14,7 @@ The following options are available:
- -g or --global makes the variables global.
- -l or --local makes the variables local.
- -m NAME or --mode-name=NAME specifies that the name NAME should be used to save/load the history file. If NAME is fish, the regular fish history will be available.
+- -n NCHARS or --nchars=NCHARS causes \c read to return after reading NCHARS characters rather than waiting for a complete line of input.
- -p PROMPT_CMD or --prompt=PROMPT_CMD uses the output of the shell command \c PROMPT_CMD as the prompt for the interactive mode. The default prompt command is set_color green; echo read; set_color normal; echo "> ".
- -s
or --shell
enables syntax highlighting, tab completions and command termination suitable for entering shellscript code in the interactive mode.
- -u
or --unexport
prevents the variables from being exported to child processes (default behaviour).
diff --git a/reader.cpp b/reader.cpp
index c45e62b23..2a2c08974 100644
--- a/reader.cpp
+++ b/reader.cpp
@@ -2955,7 +2955,7 @@ static int read_i(void)
during evaluation.
*/
- const wchar_t *tmp = reader_readline();
+ const wchar_t *tmp = reader_readline(0);
if (data->end_loop)
{
@@ -3044,7 +3044,7 @@ static wchar_t unescaped_quote(const wcstring &str, size_t pos)
}
-const wchar_t *reader_readline(void)
+const wchar_t *reader_readline(int nchars)
{
wint_t c;
int last_char=0;
@@ -3084,6 +3084,13 @@ const wchar_t *reader_readline(void)
while (!finished && !data->end_loop)
{
+ if (0 < nchars && (size_t)nchars <= data->command_line.size())
+ {
+ // we've already hit the specified character limit
+ finished = 1;
+ break;
+ }
+
/*
Sometimes strange input sequences seem to generate a zero
byte. I believe these simply mean a character was pressed
@@ -3104,12 +3111,14 @@ const wchar_t *reader_readline(void)
{
wchar_t arr[READAHEAD_MAX+1];
- int i;
+ size_t i;
+ size_t limit = 0 < nchars ? std::min((size_t)nchars - data->command_line.size(), (size_t)READAHEAD_MAX)
+ : READAHEAD_MAX;
memset(arr, 0, sizeof(arr));
arr[0] = c;
- for (i=1; icommand_line.size())
+ {
+ c = R_NULL;
+ break;
+ }
}
/* If we get something other than a repaint, then stop coalescing them */
diff --git a/reader.h b/reader.h
index f89bd50e9..3c82f5d18 100644
--- a/reader.h
+++ b/reader.h
@@ -200,11 +200,13 @@ int reader_reading_interrupted();
bool reader_thread_job_is_stale();
/**
- Read one line of input. Before calling this function, reader_push()
- must have been called in order to set up a valid reader
- environment.
+ Read one line of input. Before calling this function, reader_push() must have
+ been called in order to set up a valid reader environment. If nchars > 0,
+ return after reading that many characters even if a full line has not yet
+ been read. Note: the returned value may be longer than nchars if a single
+ keypress resulted in multiple characters being inserted into the commandline.
*/
-const wchar_t *reader_readline();
+const wchar_t *reader_readline(int nchars);
/**
Push a new reader environment.
diff --git a/tests/read.in b/tests/read.in
index f8787c5ce..f864287f3 100644
--- a/tests/read.in
+++ b/tests/read.in
@@ -75,3 +75,19 @@ print_vars ary
echo '' | read -la ary
print_vars ary
set -le IFS
+
+# read -n tests
+
+echo
+echo '# read -n tests'
+echo 'testing' | read -n 3 foo
+echo $foo
+echo 'test' | read -n 10 foo
+echo $foo
+echo 'test' | read -n 0 foo
+echo $foo
+echo 'testing' | begin; read -n 3 foo; read -n 3 bar; end
+echo $foo
+echo $bar
+echo 'test' | read -n 1 foo
+echo $foo
diff --git a/tests/read.out b/tests/read.out
index 1098045dd..0de988314 100644
--- a/tests/read.out
+++ b/tests/read.out
@@ -34,3 +34,11 @@ two
5 'h' 'e' 'l' 'l' 'o'
1 'h'
0
+
+# read -n tests
+tes
+test
+test
+tes
+tin
+t