summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Kelley <michael.a.kelley@gmail.com>2019-02-04 22:51:39 -0800
committerJunegunn Choi <junegunn.c@gmail.com>2020-03-10 00:03:34 +0900
commit7d5985baf927cdd04070679cbfbb5ff6b0728f6d (patch)
tree5b67c8ac90f2a040832c1d17b1ed10746f1d6b36
parent7c40a424c0bf5a8967816d51ead6a71a334f30bb (diff)
Make height option work under Windows (#1341)
Separate Unix & Windows code into platform specific files for light renderer
-rw-r--r--CHANGELOG.md1
-rw-r--r--src/options.go5
-rw-r--r--src/tui/light.go78
-rw-r--r--src/tui/light_unix.go97
-rw-r--r--src/tui/light_windows.go132
5 files changed, 244 insertions, 69 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b1f68751..1a16fd53 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ CHANGELOG
finder instead of printing horizontal lines above and below it.
The previous style is available via `--border=horizontal`
- Added `--pointer` and `--marker` options
+- `--height` option is now available on Windows binary (@kelleyma49)
- More keys and actions for `--bind`
- Bug fixes and improvements
- Vim plugin: Floating windows support
diff --git a/src/options.go b/src/options.go
index b87e85c2..0fd97050 100644
--- a/src/options.go
+++ b/src/options.go
@@ -11,7 +11,6 @@ import (
"github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/tui"
- "github.com/junegunn/fzf/src/util"
"github.com/mattn/go-runewidth"
"github.com/mattn/go-shellwords"
@@ -1412,8 +1411,8 @@ func validateSign(sign string, signOptName string) error {
}
func postProcessOptions(opts *Options) {
- if util.IsWindows() && opts.Height.size > 0 {
- errorExit("--height option is currently not supported on Windows")
+ if !tui.IsLightRendererSupported() && opts.Height.size > 0 {
+ errorExit("--height option is currently not supported on this platform")
}
// Default actions for CTRL-N / CTRL-P when --history is set
if opts.History != nil {
diff --git a/src/tui/light.go b/src/tui/light.go
index d8de4b66..3ff84870 100644
--- a/src/tui/light.go
+++ b/src/tui/light.go
@@ -7,7 +7,6 @@ import (
"regexp"
"strconv"
"strings"
- "syscall"
"time"
"unicode/utf8"
@@ -30,21 +29,6 @@ const consoleDevice string = "/dev/tty"
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
-func openTtyIn() *os.File {
- in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
- if err != nil {
- tty := ttyname()
- if len(tty) > 0 {
- if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
- return in
- }
- }
- fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
- os.Exit(2)
- }
- return in
-}
-
func (r *LightRenderer) stderr(str string) {
r.stderrInternal(str, true)
}
@@ -101,6 +85,13 @@ type LightRenderer struct {
y int
x int
maxHeightFunc func(int) int
+
+ // Windows only
+ ttyinChannel chan byte
+ inHandle uintptr
+ outHandle uintptr
+ origStateInput uint32
+ origStateOutput uint32
}
type LightWindow struct {
@@ -134,10 +125,6 @@ func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop in
return &r
}
-func (r *LightRenderer) fd() int {
- return int(r.ttyin.Fd())
-}
-
func (r *LightRenderer) defaultTheme() *ColorTheme {
if strings.Contains(os.Getenv("TERM"), "256") {
return Dark256
@@ -149,22 +136,6 @@ func (r *LightRenderer) defaultTheme() *ColorTheme {
return Default16
}
-func (r *LightRenderer) findOffset() (row int, col int) {
- r.csi("6n")
- r.flush()
- bytes := []byte{}
- for tries := 0; tries < offsetPollTries; tries++ {
- bytes = r.getBytesInternal(bytes, tries > 0)
- offsets := offsetRegexp.FindSubmatch(bytes)
- if len(offsets) > 3 {
- // add anything we skipped over to the input buffer
- r.buffer = append(r.buffer, offsets[1]...)
- return atoi(string(offsets[2]), 0) - 1, atoi(string(offsets[3]), 0) - 1
- }
- }
- return -1, -1
-}
-
func repeat(r rune, times int) string {
if times > 0 {
return strings.Repeat(string(r), times)
@@ -183,13 +154,9 @@ func atoi(s string, defaultValue int) int {
func (r *LightRenderer) Init() {
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
- fd := r.fd()
- origState, err := terminal.GetState(fd)
- if err != nil {
+ if err := r.initPlatform(); err != nil {
errorExit(err.Error())
}
- r.origState = origState
- terminal.MakeRaw(fd)
r.updateTerminalSize()
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
@@ -262,28 +229,6 @@ func getEnv(name string, defaultValue int) int {
return atoi(env, defaultValue)
}
-func (r *LightRenderer) updateTerminalSize() {
- width, height, err := terminal.GetSize(r.fd())
- if err == nil {
- r.width = width
- r.height = r.maxHeightFunc(height)
- } else {
- r.width = getEnv("COLUMNS", defaultWidth)
- r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
- }
-}
-
-func (r *LightRenderer) getch(nonblock bool) (int, bool) {
- b := make([]byte, 1)
- fd := r.fd()
- util.SetNonblock(r.ttyin, nonblock)
- _, err := util.Read(fd, b)
- if err != nil {
- return 0, false
- }
- return int(b[0]), true
-}
-
func (r *LightRenderer) getBytes() []byte {
return r.getBytesInternal(r.buffer, false)
}
@@ -604,7 +549,7 @@ func (r *LightRenderer) rmcup() {
}
func (r *LightRenderer) Pause(clear bool) {
- terminal.Restore(r.fd(), r.origState)
+ r.restoreTerminal()
if clear {
if r.fullscreen {
r.rmcup()
@@ -617,7 +562,7 @@ func (r *LightRenderer) Pause(clear bool) {
}
func (r *LightRenderer) Resume(clear bool) {
- terminal.MakeRaw(r.fd())
+ r.setupTerminal()
if clear {
if r.fullscreen {
r.smcup()
@@ -671,7 +616,8 @@ func (r *LightRenderer) Close() {
r.csi("?1000l")
}
r.flush()
- terminal.Restore(r.fd(), r.origState)
+ r.closePlatform()
+ r.restoreTerminal()
}
func (r *LightRenderer) MaxX() int {
diff --git a/src/tui/light_unix.go b/src/tui/light_unix.go
new file mode 100644
index 00000000..e4ce6313
--- /dev/null
+++ b/src/tui/light_unix.go
@@ -0,0 +1,97 @@
+// +build !windows
+
+package tui
+
+import (
+ "fmt"
+ "os"
+ "syscall"
+
+ "github.com/junegunn/fzf/src/util"
+ "golang.org/x/crypto/ssh/terminal"
+)
+
+func IsLightRendererSupported() bool {
+ return true
+}
+
+func (r *LightRenderer) fd() int {
+ return int(r.ttyin.Fd())
+}
+
+func (r *LightRenderer) initPlatform() error {
+ fd := r.fd()
+ origState, err := terminal.GetState(fd)
+ if err != nil {
+ return err
+ }
+ r.origState = origState
+ terminal.MakeRaw(fd)
+ return nil
+}
+
+func (r *LightRenderer) closePlatform() {
+ // NOOP
+}
+
+func openTtyIn() *os.File {
+ in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
+ if err != nil {
+ tty := ttyname()
+ if len(tty) > 0 {
+ if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
+ return in
+ }
+ }
+ fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
+ os.Exit(2)
+ }
+ return in
+}
+
+func (r *LightRenderer) setupTerminal() {
+ terminal.MakeRaw(r.fd())
+}
+
+func (r *LightRenderer) restoreTerminal() {
+ terminal.Restore(r.fd(), r.origState)
+}
+
+func (r *LightRenderer) updateTerminalSize() {
+ width, height, err := terminal.GetSize(r.fd())
+
+ if err == nil {
+ r.width = width
+ r.height = r.maxHeightFunc(height)
+ } else {
+ r.width = getEnv("COLUMNS", defaultWidth)
+ r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
+ }
+}
+
+func (r *LightRenderer) findOffset() (row int, col int) {
+ r.csi("6n")
+ r.flush()
+ bytes := []byte{}
+ for tries := 0; tries < offsetPollTries; tries++ {
+ bytes = r.getBytesInternal(bytes, tries > 0)
+ offsets := offsetRegexp.FindSubmatch(bytes)
+ if len(offsets) > 3 {
+ // Add anything we skipped over to the input buffer
+ r.buffer = append(r.buffer, offsets[1]...)
+ return atoi(string(offsets[2]), 0) - 1, atoi(string(offsets[3]), 0) - 1
+ }
+ }
+ return -1, -1
+}
+
+func (r *LightRenderer) getch(nonblock bool) (int, bool) {
+ b := make([]byte, 1)
+ fd := r.fd()
+ util.SetNonblock(r.ttyin, nonblock)
+ _, err := util.Read(fd, b)
+ if err != nil {
+ return 0, false
+ }
+ return int(b[0]), true
+}
diff --git a/src/tui/light_windows.go b/src/tui/light_windows.go
new file mode 100644
index 00000000..6f876af2
--- /dev/null
+++ b/src/tui/light_windows.go
@@ -0,0 +1,132 @@
+//+build windows
+
+package tui
+
+import (
+ "os"
+ "syscall"
+
+ "github.com/junegunn/fzf/src/util"
+ "golang.org/x/sys/windows"
+)
+
+var (
+ consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)
+ consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)
+)
+
+// IsLightRendererSupported checks to see if the Light renderer is supported
+func IsLightRendererSupported() bool {
+ var oldState uint32
+ // enable vt100 emulation (https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences)
+ if windows.GetConsoleMode(windows.Stderr, &oldState) != nil {
+ return false
+ }
+ // attempt to set mode to determine if we support VT 100 codes. This will work on newer Windows 10
+ // version:
+ canSetVt100 := windows.SetConsoleMode(windows.Stderr, oldState|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) == nil
+ var checkState uint32
+ if windows.GetConsoleMode(windows.Stderr, &checkState) != nil ||
+ (checkState&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING {
+ return false
+ }
+ windows.SetConsoleMode(windows.Stderr, oldState)
+ return canSetVt100
+}
+
+func (r *LightRenderer) initPlatform() error {
+ //outHandle := windows.Stdout
+ outHandle, _ := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
+ // enable vt100 emulation (https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences)
+ if err := windows.GetConsoleMode(windows.Handle(outHandle), &r.origStateOutput); err != nil {
+ return err
+ }
+ r.outHandle = uintptr(outHandle)
+ inHandle, _ := syscall.Open("CONIN$", syscall.O_RDWR, 0)
+ if err := windows.GetConsoleMode(windows.Handle(inHandle), &r.origStateInput); err != nil {
+ return err
+ }
+ r.inHandle = uintptr(inHandle)
+
+ r.setupTerminal()
+
+ // channel for non-blocking reads. Buffer to make sure
+ // we get the ESC sets:
+ r.ttyinChannel = make(chan byte, 12)
+
+ // the following allows for non-blocking IO.
+ // syscall.SetNonblock() is a NOOP under Windows.
+ go func() {
+ fd := int(r.inHandle)
+ b := make([]byte, 1)
+ for {
+ // HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
+ _ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
+
+ _, err := util.Read(fd, b)
+ if err == nil {
+ r.ttyinChannel <- b[0]
+ }
+ }
+ }()
+
+ return nil
+}
+
+func (r *LightRenderer) closePlatform() {
+ windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
+ windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
+}
+
+func openTtyIn() *os.File {
+ // not used
+ return nil
+}
+
+func (r *LightRenderer) setupTerminal() error {
+ if err := windows.SetConsoleMode(windows.Handle(r.outHandle), consoleFlagsOutput); err != nil {
+ return err
+ }
+ return windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
+}
+
+func (r *LightRenderer) restoreTerminal() error {
+ if err := windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput); err != nil {
+ return err
+ }
+ return windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
+}
+
+func (r *LightRenderer) updateTerminalSize() {
+ var bufferInfo windows.ConsoleScreenBufferInfo
+ if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
+ r.width = getEnv("COLUMNS", defaultWidth)
+ r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
+
+ } else {
+ r.width = int(bufferInfo.Window.Right - bufferInfo.Window.Left)
+ r.height = r.maxHeightFunc(int(bufferInfo.Window.Bottom - bufferInfo.Window.Top))
+ }
+}
+
+func (r *LightRenderer) findOffset() (row int, col int) {
+ var bufferInfo windows.ConsoleScreenBufferInfo
+ if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
+ return -1, -1
+ }
+ return int(bufferInfo.CursorPosition.X), int(bufferInfo.CursorPosition.Y)
+}
+
+func (r *LightRenderer) getch(nonblock bool) (int, bool) {
+ if nonblock {
+ select {
+ case bc := <-r.ttyinChannel:
+ return int(bc), true
+ default:
+ return 0, false
+ }
+ } else {
+ bc := <-r.ttyinChannel
+ return int(bc), true
+ }
+}