diff --git a/Makefile.in b/Makefile.in
index c016d9f7a..b67ffac01 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -91,7 +91,8 @@ FISH_OBJS := function.o builtin.o complete.o env.o exec.o expand.o \
env_universal.o env_universal_common.o input_common.o event.o \
signal.o io.o parse_util.o common.o screen.o path.o autoload.o \
parser_keywords.o iothread.o color.o postfork.o \
- builtin_test.o parse_tree.o parse_productions.o parse_execution.cpp
+ builtin_test.o parse_tree.o parse_productions.o parse_execution.cpp \
+ pager.cpp
FISH_INDENT_OBJS := fish_indent.o print_help.o common.o \
parser_keywords.o wutil.o tokenizer.o
diff --git a/builtin_commandline.cpp b/builtin_commandline.cpp
index f121cb644..564eee7bc 100644
--- a/builtin_commandline.cpp
+++ b/builtin_commandline.cpp
@@ -149,7 +149,7 @@ static void write_part(const wchar_t *begin,
{
wchar_t *buff = wcsndup(begin, end-begin);
// fwprintf( stderr, L"Subshell: %ls, end char %lc\n", buff, *end );
- wcstring out;
+ wcstring out;
tokenizer_t tok(buff, TOK_ACCEPT_UNFINISHED);
for (; tok_has_next(&tok); tok_next(&tok))
{
@@ -213,6 +213,7 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv)
int cursor_mode = 0;
int line_mode = 0;
int search_mode = 0;
+ int paging_mode = 0;
const wchar_t *begin, *end;
current_buffer = (wchar_t *)builtin_complete_get_temporary_buffer();
@@ -251,71 +252,24 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv)
static const struct woption
long_options[] =
{
- {
- L"append", no_argument, 0, 'a'
- }
- ,
- {
- L"insert", no_argument, 0, 'i'
- }
- ,
- {
- L"replace", no_argument, 0, 'r'
- }
- ,
- {
- L"current-job", no_argument, 0, 'j'
- }
- ,
- {
- L"current-process", no_argument, 0, 'p'
- }
- ,
- {
- L"current-token", no_argument, 0, 't'
- }
- ,
- {
- L"current-buffer", no_argument, 0, 'b'
- }
- ,
- {
- L"cut-at-cursor", no_argument, 0, 'c'
- }
- ,
- {
- L"function", no_argument, 0, 'f'
- }
- ,
- {
- L"tokenize", no_argument, 0, 'o'
- }
- ,
- {
- L"help", no_argument, 0, 'h'
- }
- ,
- {
- L"input", required_argument, 0, 'I'
- }
- ,
- {
- L"cursor", no_argument, 0, 'C'
- }
- ,
- {
- L"line", no_argument, 0, 'L'
- }
- ,
- {
- L"search-mode", no_argument, 0, 'S'
- }
- ,
- {
- 0, 0, 0, 0
- }
- }
- ;
+ { L"append", no_argument, 0, 'a' },
+ { L"insert", no_argument, 0, 'i' },
+ { L"replace", no_argument, 0, 'r' },
+ { L"current-job", no_argument, 0, 'j' },
+ { L"current-process", no_argument, 0, 'p' },
+ { L"current-token", no_argument, 0, 't' },
+ { L"current-buffer", no_argument, 0, 'b' },
+ { L"cut-at-cursor", no_argument, 0, 'c' },
+ { L"function", no_argument, 0, 'f' },
+ { L"tokenize", no_argument, 0, 'o' },
+ { L"help", no_argument, 0, 'h' },
+ { L"input", required_argument, 0, 'I' },
+ { L"cursor", no_argument, 0, 'C' },
+ { L"line", no_argument, 0, 'L' },
+ { L"search-mode", no_argument, 0, 'S' },
+ { L"paging-mode", no_argument, 0, 'P' },
+ { 0, 0, 0, 0 }
+ };
int opt_index = 0;
@@ -397,6 +351,10 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv)
case 'S':
search_mode = 1;
break;
+
+ case 'P':
+ paging_mode = 1;
+ break;
case 'h':
builtin_print_help(parser, argv[0], stdout_buffer);
@@ -415,7 +373,7 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv)
/*
Check for invalid switch combinations
*/
- if (buffer_part || cut_at_cursor || append_mode || tokenize || cursor_mode || line_mode || search_mode)
+ if (buffer_part || cut_at_cursor || append_mode || tokenize || cursor_mode || line_mode || search_mode || paging_mode)
{
append_format(stderr_buffer,
BUILTIN_ERR_COMBO,
@@ -464,7 +422,7 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv)
/*
Check for invalid switch combinations
*/
- if ((search_mode || line_mode || cursor_mode) && (argc-woptind > 1))
+ if ((search_mode || line_mode || cursor_mode || paging_mode) && (argc-woptind > 1))
{
append_format(stderr_buffer,
@@ -475,7 +433,7 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv)
return 1;
}
- if ((buffer_part || tokenize || cut_at_cursor) && (cursor_mode || line_mode || search_mode))
+ if ((buffer_part || tokenize || cut_at_cursor) && (cursor_mode || line_mode || search_mode || paging_mode))
{
append_format(stderr_buffer,
BUILTIN_ERR_COMBO,
@@ -564,7 +522,12 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv)
if (search_mode)
{
- return !reader_search_mode();
+ return ! reader_search_mode();
+ }
+
+ if (paging_mode)
+ {
+ return ! reader_has_pager_contents();
}
diff --git a/common.h b/common.h
index 86c5bc6c9..3e9be1063 100644
--- a/common.h
+++ b/common.h
@@ -87,6 +87,37 @@ enum
};
typedef unsigned int escape_flags_t;
+/* Directions */
+enum selection_direction_t
+{
+ /* visual directions */
+ direction_north,
+ direction_east,
+ direction_south,
+ direction_west,
+
+ /* logical directions */
+ direction_next,
+ direction_prev,
+
+ /* special value that means deselect */
+ direction_deselect
+};
+
+inline bool selection_direction_is_cardinal(selection_direction_t dir)
+{
+ switch (dir)
+ {
+ case direction_north:
+ case direction_east:
+ case direction_south:
+ case direction_west:
+ return true;
+ default:
+ return false;
+ }
+}
+
/**
Helper macro for errors
*/
diff --git a/doc_src/commandline.txt b/doc_src/commandline.txt
index ab771e96d..1d13f79e5 100644
--- a/doc_src/commandline.txt
+++ b/doc_src/commandline.txt
@@ -57,6 +57,16 @@ If \c commandline is called during a call to complete a given string
using complete -C STRING
, \c commandline will consider the
specified string to be the current contents of the command line.
+The following options output metadata about the commandline state:
+
+- \c -L or \c --line print the line that the cursor is on, with the topmost
+line starting at 1
+- \c -S or \c --search-mode evaluates to true if the commandline is performing
+a history search
+- \c -P or \c --paging-mode evaluates to true if the commandline is showing
+pager contents, such as tab completions
+
+
\subsection commandline-example Example
commandline -j $history[3] replaces the job under the cursor with the
diff --git a/fish.cpp b/fish.cpp
index 77686195a..bf6470f8a 100644
--- a/fish.cpp
+++ b/fish.cpp
@@ -182,7 +182,7 @@ static struct config_paths_t determine_config_directory_paths(const char *argv0)
{
wcstring base_path = str2wcstring(exec_path);
base_path.resize(base_path.size() - strlen(suffix));
-
+
paths.data = base_path + L"/share/fish";
paths.sysconf = base_path + L"/etc/fish";
paths.doc = base_path + L"/share/doc/fish";
diff --git a/fish.xcodeproj/project.pbxproj b/fish.xcodeproj/project.pbxproj
index ceb694ee7..af6315f0e 100644
--- a/fish.xcodeproj/project.pbxproj
+++ b/fish.xcodeproj/project.pbxproj
@@ -62,6 +62,7 @@
D01A2D24169B736200767098 /* man1 in Copy Files */ = {isa = PBXBuildFile; fileRef = D01A2D23169B730A00767098 /* man1 */; };
D01A2D25169B737700767098 /* man1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = D01A2D23169B730A00767098 /* man1 */; };
D031890C15E36E4600D9CC39 /* base in Resources */ = {isa = PBXBuildFile; fileRef = D031890915E36D9800D9CC39 /* base */; };
+ D032388B1849D1980032CF2C /* pager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D03238891849D1980032CF2C /* pager.cpp */; };
D033781115DC6D4C00A634BA /* completions in CopyFiles */ = {isa = PBXBuildFile; fileRef = D025C02715D1FEA100B9DB63 /* completions */; };
D033781215DC6D5200A634BA /* functions in CopyFiles */ = {isa = PBXBuildFile; fileRef = D025C02815D1FEA100B9DB63 /* functions */; };
D033781315DC6D5400A634BA /* tools in CopyFiles */ = {isa = PBXBuildFile; fileRef = D025C02915D1FEA100B9DB63 /* tools */; };
@@ -386,6 +387,8 @@
D025C02815D1FEA100B9DB63 /* functions */ = {isa = PBXFileReference; lastKnownFileType = folder; name = functions; path = share/functions; sourceTree = ""; };
D025C02915D1FEA100B9DB63 /* tools */ = {isa = PBXFileReference; lastKnownFileType = folder; name = tools; path = share/tools; sourceTree = ""; };
D031890915E36D9800D9CC39 /* base */ = {isa = PBXFileReference; lastKnownFileType = text; path = base; sourceTree = BUILT_PRODUCTS_DIR; };
+ D03238891849D1980032CF2C /* pager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pager.cpp; sourceTree = ""; };
+ D032388A1849D1980032CF2C /* pager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pager.h; sourceTree = ""; };
D03EE83814DF88B200FC7150 /* lru.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = lru.h; sourceTree = ""; };
D052D8091868F7FC003ABCBD /* parse_execution.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = parse_execution.cpp; sourceTree = ""; };
D052D80A1868F7FC003ABCBD /* parse_execution.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = parse_execution.h; sourceTree = ""; };
@@ -699,6 +702,8 @@
D0A0855013B3ACEE0099B651 /* mimedb.cpp */,
D0A0851A13B3ACEE0099B651 /* output.h */,
D0A0855113B3ACEE0099B651 /* output.cpp */,
+ D032388A1849D1980032CF2C /* pager.h */,
+ D03238891849D1980032CF2C /* pager.cpp */,
D0A0851B13B3ACEE0099B651 /* parse_util.h */,
D0A0855213B3ACEE0099B651 /* parse_util.cpp */,
D0A0851C13B3ACEE0099B651 /* parser_keywords.h */,
@@ -1254,6 +1259,7 @@
D0D02A7915983888008E62BD /* intern.cpp in Sources */,
D0D02A7A15983916008E62BD /* env_universal.cpp in Sources */,
D0D02A7B15983928008E62BD /* env_universal_common.cpp in Sources */,
+ D032388B1849D1980032CF2C /* pager.cpp in Sources */,
D0D02A89159839DF008E62BD /* fish.cpp in Sources */,
D0C52F371765284C00BFAB82 /* parse_tree.cpp in Sources */,
D0FE8EE8179FB760008C9F21 /* parse_productions.cpp in Sources */,
diff --git a/fish_tests.cpp b/fish_tests.cpp
index 13d111d31..6082f0928 100644
--- a/fish_tests.cpp
+++ b/fish_tests.cpp
@@ -60,6 +60,7 @@
#include "signal.h"
#include "parse_tree.h"
#include "parse_util.h"
+#include "pager.h"
static const char * const * s_arguments;
static int s_test_run_count = 0;
@@ -1098,6 +1099,99 @@ static void test_path()
if (! paths_are_equivalent(L"/", L"/")) err(L"Bug in canonical PATH code on line %ld", (long)__LINE__);
}
+static void test_pager_navigation()
+{
+ say(L"Testing pager navigation");
+
+ /* Generate 19 strings of width 10. There's 2 spaces between completions, and our term size is 80; these can therefore fit into 6 columns (6 * 12 - 2 = 70) or 5 columns (58) but not 7 columns (7 * 12 - 2 = 82).
+
+ You can simulate this test by creating 19 files named "file00.txt" through "file_18.txt".
+ */
+ completion_list_t completions;
+ for (size_t i=0; i < 19; i++)
+ {
+ append_completion(completions, L"abcdefghij");
+ }
+
+ pager_t pager;
+ pager.set_completions(completions);
+ pager.set_term_size(80, 24);
+ page_rendering_t render = pager.render();
+
+ if (render.term_width != 80)
+ err(L"Wrong term width");
+ if (render.term_height != 24)
+ err(L"Wrong term height");
+
+ size_t rows = 4, cols = 5;
+
+ /* We have 19 completions. We can fit into 6 columns with 4 rows or 5 columns with 4 rows; the second one is better and so is what we ought to have picked. */
+ if (render.rows != rows)
+ err(L"Wrong row count");
+ if (render.cols != cols)
+ err(L"Wrong column count");
+
+ /* Initially expect to have no completion index */
+ if (render.selected_completion_idx != (size_t)(-1))
+ {
+ err(L"Wrong initial selection");
+ }
+
+ /* Here are navigation directions and where we expect the selection to be */
+ const struct
+ {
+ selection_direction_t dir;
+ size_t sel;
+ }
+ cmds[] =
+ {
+ /* Tab completion to get into the list */
+ {direction_next, 0},
+
+ /* Westward motion in upper left wraps along the top row */
+ {direction_west, 16},
+ {direction_east, 1},
+
+ /* "Next" motion goes down the column */
+ {direction_next, 2},
+ {direction_next, 3},
+
+ {direction_west, 18},
+ {direction_east, 3},
+ {direction_east, 7},
+ {direction_east, 11},
+ {direction_east, 15},
+ {direction_east, 3},
+
+ {direction_west, 18},
+ {direction_east, 3},
+
+ /* Eastward motion wraps along the bottom, westward goes to the prior column */
+ {direction_east, 7},
+ {direction_east, 11},
+ {direction_east, 15},
+ {direction_east, 3},
+
+ /* Column memory */
+ {direction_west, 18},
+ {direction_south, 15},
+ {direction_north, 18},
+ {direction_west, 14},
+ {direction_south, 15},
+ {direction_north, 14}
+ };
+ for (size_t i=0; i < sizeof cmds / sizeof *cmds; i++)
+ {
+ pager.select_next_completion_in_direction(cmds[i].dir, render);
+ pager.update_rendering(&render);
+ if (cmds[i].sel != render.selected_completion_idx)
+ {
+ err(L"For command %lu, expected selection %lu, but found instead %lu\n", i, cmds[i].sel, render.selected_completion_idx);
+ }
+ }
+
+}
+
enum word_motion_t
{
word_motion_left,
@@ -2716,6 +2810,7 @@ int main(int argc, char **argv)
if (should_test_function("abbreviations")) test_abbreviations();
if (should_test_function("test")) test_test();
if (should_test_function("path")) test_path();
+ if (should_test_function("pager_navigation")) test_pager_navigation();
if (should_test_function("word_motion")) test_word_motion();
if (should_test_function("is_potential_path")) test_is_potential_path();
if (should_test_function("colors")) test_colors();
diff --git a/highlight.cpp b/highlight.cpp
index 4fe2a5ba9..f2ae460c0 100644
--- a/highlight.cpp
+++ b/highlight.cpp
@@ -61,7 +61,14 @@ static const wchar_t * const highlight_var[] =
L"fish_color_escape",
L"fish_color_quote",
L"fish_color_redirection",
- L"fish_color_autosuggestion"
+ L"fish_color_autosuggestion",
+
+ L"fish_pager_color_prefix",
+ L"fish_pager_color_completion",
+ L"fish_pager_color_description",
+ L"fish_pager_color_progress",
+ L"fish_pager_color_secondary"
+
};
/* If the given path looks like it's relative to the working directory, then prepend that working directory. */
@@ -354,7 +361,10 @@ bool plain_statement_get_expanded_command(const wcstring &src, const parse_node_
rgb_color_t highlight_get_color(highlight_spec_t highlight, bool is_background)
{
rgb_color_t result = rgb_color_t::normal();
-
+
+ /* If sloppy_background is set, then we look at the foreground color even if is_background is set */
+ bool treat_as_background = is_background && ! (highlight & highlight_modifier_sloppy_background);
+
/* Get the primary variable */
size_t idx = highlight_get_primary(highlight);
if (idx >= VAR_COUNT)
@@ -370,9 +380,9 @@ rgb_color_t highlight_get_color(highlight_spec_t highlight, bool is_background)
val_wstr = env_get_string(highlight_var[0]);
if (! val_wstr.missing())
- result = parse_color(val_wstr, is_background);
+ result = parse_color(val_wstr, treat_as_background);
- /* Handle modifiers. Just one for now */
+ /* Handle modifiers. */
if (highlight & highlight_modifier_valid_path)
{
env_var_t val2_wstr = env_get_string(L"fish_color_valid_path");
diff --git a/highlight.h b/highlight.h
index cc5651745..dfe3f9310 100644
--- a/highlight.h
+++ b/highlight.h
@@ -29,10 +29,18 @@ enum
highlight_spec_redirection, //redirection
highlight_spec_autosuggestion, //autosuggestion
+ // Pager support
+ highlight_spec_pager_prefix,
+ highlight_spec_pager_completion,
+ highlight_spec_pager_description,
+ highlight_spec_pager_progress,
+ highlight_spec_pager_secondary,
+
HIGHLIGHT_SPEC_PRIMARY_MASK = 0xFF,
/* The following values are modifiers */
highlight_modifier_valid_path = 0x100,
+ highlight_modifier_sloppy_background = 0x200, //hackish, indicates that we should treat a foreground color as background, per certain historical behavior
/* Very special value */
highlight_spec_invalid = 0xFFFF
@@ -47,6 +55,7 @@ inline highlight_spec_t highlight_get_primary(highlight_spec_t val)
inline highlight_spec_t highlight_make_background(highlight_spec_t val)
{
+ assert(val >> 16 == 0); //should have nothing in upper bits, otherwise this is already a background
return val << 16;
}
diff --git a/pager.cpp b/pager.cpp
new file mode 100644
index 000000000..f4ecce7c6
--- /dev/null
+++ b/pager.cpp
@@ -0,0 +1,872 @@
+#include "config.h"
+
+#include "pager.h"
+#include "highlight.h"
+#include "input_common.h"
+#include
+#include