git
/
pager.c
306 строк · 5.9 Кб
1#include "git-compat-util.h"2#include "config.h"3#include "editor.h"4#include "pager.h"5#include "run-command.h"6#include "sigchain.h"7#include "alias.h"8
9int pager_use_color = 1;10
11#ifndef DEFAULT_PAGER12#define DEFAULT_PAGER "less"13#endif14
15static struct child_process pager_process;16static char *pager_program;17static int old_fd1 = -1, old_fd2 = -1;18
19/* Is the value coming back from term_columns() just a guess? */
20static int term_columns_guessed;21
22
23static void close_pager_fds(void)24{
25/* signal EOF to pager */26close(1);27if (old_fd2 != -1)28close(2);29}
30
31static void finish_pager(void)32{
33fflush(stdout);34fflush(stderr);35close_pager_fds();36finish_command(&pager_process);37}
38
39static void wait_for_pager_atexit(void)40{
41if (old_fd1 == -1)42return;43
44finish_pager();45}
46
47void wait_for_pager(void)48{
49if (old_fd1 == -1)50return;51
52finish_pager();53sigchain_pop_common();54unsetenv("GIT_PAGER_IN_USE");55dup2(old_fd1, 1);56close(old_fd1);57old_fd1 = -1;58if (old_fd2 != -1) {59dup2(old_fd2, 2);60close(old_fd2);61old_fd2 = -1;62}63}
64
65static void wait_for_pager_signal(int signo)66{
67if (old_fd1 == -1)68return;69
70close_pager_fds();71finish_command_in_signal(&pager_process);72sigchain_pop(signo);73raise(signo);74}
75
76static int core_pager_config(const char *var, const char *value,77const struct config_context *ctx UNUSED,78void *data UNUSED)79{
80if (!strcmp(var, "core.pager"))81return git_config_string(&pager_program, var, value);82return 0;83}
84
85const char *git_pager(int stdout_is_tty)86{
87const char *pager;88
89if (!stdout_is_tty)90return NULL;91
92pager = getenv("GIT_PAGER");93if (!pager) {94if (!pager_program)95read_early_config(core_pager_config, NULL);96pager = pager_program;97}98if (!pager)99pager = getenv("PAGER");100if (!pager)101pager = DEFAULT_PAGER;102if (!*pager || !strcmp(pager, "cat"))103pager = NULL;104
105return pager;106}
107
108static void setup_pager_env(struct strvec *env)109{
110const char **argv;111int i;112char *pager_env = xstrdup(PAGER_ENV);113int n = split_cmdline(pager_env, &argv);114
115if (n < 0)116die("malformed build-time PAGER_ENV: %s",117split_cmdline_strerror(n));118
119for (i = 0; i < n; i++) {120char *cp = strchr(argv[i], '=');121
122if (!cp)123die("malformed build-time PAGER_ENV");124
125*cp = '\0';126if (!getenv(argv[i])) {127*cp = '=';128strvec_push(env, argv[i]);129}130}131free(pager_env);132free(argv);133}
134
135void prepare_pager_args(struct child_process *pager_process, const char *pager)136{
137strvec_push(&pager_process->args, pager);138pager_process->use_shell = 1;139setup_pager_env(&pager_process->env);140pager_process->trace2_child_class = "pager";141}
142
143void setup_pager(void)144{
145static int once = 0;146const char *pager = git_pager(isatty(1));147
148if (!pager)149return;150
151/*152* After we redirect standard output, we won't be able to use an ioctl
153* to get the terminal size. Let's grab it now, and then set $COLUMNS
154* to communicate it to any sub-processes.
155*/
156{157char buf[64];158xsnprintf(buf, sizeof(buf), "%d", term_columns());159if (!term_columns_guessed)160setenv("COLUMNS", buf, 0);161}162
163setenv("GIT_PAGER_IN_USE", "true", 1);164
165child_process_init(&pager_process);166
167/* spawn the pager */168prepare_pager_args(&pager_process, pager);169pager_process.in = -1;170strvec_push(&pager_process.env, "GIT_PAGER_IN_USE");171if (start_command(&pager_process))172die("unable to execute pager '%s'", pager);173
174/* original process continues, but writes to the pipe */175old_fd1 = dup(1);176dup2(pager_process.in, 1);177if (isatty(2)) {178old_fd2 = dup(2);179dup2(pager_process.in, 2);180}181close(pager_process.in);182
183sigchain_push_common(wait_for_pager_signal);184
185if (!once) {186once++;187atexit(wait_for_pager_atexit);188}189}
190
191int pager_in_use(void)192{
193return git_env_bool("GIT_PAGER_IN_USE", 0);194}
195
196/*
197* Return cached value (if set) or $COLUMNS environment variable (if
198* set and positive) or ioctl(1, TIOCGWINSZ).ws_col (if positive),
199* and default to 80 if all else fails.
200*/
201int term_columns(void)202{
203static int term_columns_at_startup;204
205char *col_string;206int n_cols;207
208if (term_columns_at_startup)209return term_columns_at_startup;210
211term_columns_at_startup = 80;212term_columns_guessed = 1;213
214col_string = getenv("COLUMNS");215if (col_string && (n_cols = atoi(col_string)) > 0) {216term_columns_at_startup = n_cols;217term_columns_guessed = 0;218}219#ifdef TIOCGWINSZ220else {221struct winsize ws;222if (!ioctl(1, TIOCGWINSZ, &ws) && ws.ws_col) {223term_columns_at_startup = ws.ws_col;224term_columns_guessed = 0;225}226}227#endif228
229return term_columns_at_startup;230}
231
232/*
233* Clear the entire line, leave cursor in first column.
234*/
235void term_clear_line(void)236{
237if (!isatty(2))238return;239if (is_terminal_dumb())240/*241* Fall back to print a terminal width worth of space
242* characters (hoping that the terminal is still as wide
243* as it was upon the first call to term_columns()).
244*/
245fprintf(stderr, "\r%*s\r", term_columns(), "");246else247/*248* On non-dumb terminals use an escape sequence to clear
249* the whole line, no matter how wide the terminal.
250*/
251fputs("\r\033[K", stderr);252}
253
254/*
255* How many columns do we need to show this number in decimal?
256*/
257int decimal_width(uintmax_t number)258{
259int width;260
261for (width = 1; number >= 10; width++)262number /= 10;263return width;264}
265
266struct pager_command_config_data {267const char *cmd;268int want;269char *value;270};271
272static int pager_command_config(const char *var, const char *value,273const struct config_context *ctx UNUSED,274void *vdata)275{
276struct pager_command_config_data *data = vdata;277const char *cmd;278
279if (skip_prefix(var, "pager.", &cmd) && !strcmp(cmd, data->cmd)) {280int b = git_parse_maybe_bool(value);281if (b >= 0)282data->want = b;283else {284data->want = 1;285data->value = xstrdup(value);286}287}288
289return 0;290}
291
292/* returns 0 for "no pager", 1 for "use pager", and -1 for "not specified" */
293int check_pager_config(const char *cmd)294{
295struct pager_command_config_data data;296
297data.cmd = cmd;298data.want = -1;299data.value = NULL;300
301read_early_config(pager_command_config, &data);302
303if (data.value)304pager_program = data.value;305return data.want;306}
307