deal with broken ttys on MS Windows

The tty device timestamps on MS Windows aren't usable because they're always
the current time. So fish can't use them to decide if the entire prompt needs
to be repainted.

Fixes #2859
This commit is contained in:
Kurtis Rader 2016-06-19 22:05:01 -07:00
parent 098f6d01c4
commit 82c56bc64b
4 changed files with 51 additions and 2 deletions

View file

@ -67,6 +67,7 @@ bool g_profiling_active = false;
const wchar_t *program_name; const wchar_t *program_name;
int debug_level=1; int debug_level=1;
bool has_working_tty_timestamps = true;
/** /**
This struct maintains the current state of the terminal size. It is updated on demand after receiving a SIGWINCH. This struct maintains the current state of the terminal size. It is updated on demand after receiving a SIGWINCH.

View file

@ -25,6 +25,13 @@
#include "config.h" #include "config.h"
#include "fallback.h" #include "fallback.h"
// Define a symbol we can use elsewhere in our code to determine if we're being built on MS Windows
// under Cygwin.
#if defined(_WIN32) || defined(_WIN64) || defined(WIN32) || defined(__CYGWIN__) || \
defined(__WIN32__)
#define OS_IS_CYGWIN
#endif
/** /**
Avoid writing the type name twice in a common "static_cast-initialization". Avoid writing the type name twice in a common "static_cast-initialization".
Caveat: This doesn't work with type names containing commas! Caveat: This doesn't work with type names containing commas!
@ -196,6 +203,10 @@ extern const wchar_t *program_name;
void read_ignore(int fd, void *buff, size_t count); void read_ignore(int fd, void *buff, size_t count);
void write_ignore(int fd, const void *buff, size_t count); void write_ignore(int fd, const void *buff, size_t count);
/// Set to false at run-time if it's been determined we can't trust the last modified timestamp on
/// the tty.
extern bool has_working_tty_timestamps;
/** /**
This macro is used to check that an input argument is not null. It This macro is used to check that an input argument is not null. It
is a bit lika a non-fatal form of assert. Instead of exit-ing on is a bit lika a non-fatal form of assert. Instead of exit-ing on

View file

@ -468,6 +468,36 @@ static int fish_parse_opt(int argc, char **argv, std::vector<std::string> *cmds)
return optind; return optind;
} }
/// Various things we need to initialize at run-time that don't really fit any of the other init
/// routines.
static void misc_init() {
#ifdef OS_IS_CYGWIN
// MS Windows tty devices do not have either a read or write timestamp. Those respective fields
// of `struct stat` are always the current time. Which means we can't use them. So we assume no
// external program has written to the terminal behind our back. This makes multiline prompts
// usable. See issue #2859.
has_working_tty_timestamps = false;
#else
// This covers Windows Subsystem for Linux (WSL).
int fd = open("/proc/sys/kernel/osrelease", O_RDONLY);
if (fd != -1) {
const char *magic_suffix = "Microsoft";
int len_magic_suffix = strlen(magic_suffix);
char osrelease[128] = {0};
int len_osrelease = read(fd, osrelease, sizeof(osrelease) - 1);
if (osrelease[len_osrelease - 1] == '\n') {
osrelease[len_osrelease - 1] = '\0';
len_osrelease--;
}
if (len_osrelease >= len_magic_suffix &&
strcmp(magic_suffix, osrelease + len_osrelease - len_magic_suffix) == 0) {
has_working_tty_timestamps = false;
}
close(fd);
}
#endif // OS_IS_MS_WINDOWS
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
int res=1; int res=1;
@ -519,6 +549,7 @@ int main(int argc, char **argv)
history_init(); history_init();
/* For setcolor to support term256 in config.fish (#1022) */ /* For setcolor to support term256 in config.fish (#1022) */
update_fish_color_support(); update_fish_color_support();
misc_init();
parser_t &parser = parser_t::principal_parser(); parser_t &parser = parser_t::principal_parser();

View file

@ -459,11 +459,17 @@ static void s_check_status(screen_t *s)
{ {
fflush(stdout); fflush(stdout);
fflush(stderr); fflush(stderr);
if (!has_working_tty_timestamps) {
// We can't reliably determine if the terminal has been written to behind our back so we
// just assume that hasn't happened and hope for the best. This is important for multi-line
// prompts to work correctly.
return;
}
fstat(1, &s->post_buff_1); fstat(1, &s->post_buff_1);
fstat(2, &s->post_buff_2); fstat(2, &s->post_buff_2);
int changed = (s->prev_buff_1.st_mtime != s->post_buff_1.st_mtime) || bool changed = (s->prev_buff_1.st_mtime != s->post_buff_1.st_mtime) ||
(s->prev_buff_2.st_mtime != s->post_buff_2.st_mtime); (s->prev_buff_2.st_mtime != s->post_buff_2.st_mtime);
#if defined HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC #if defined HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC