mirror of
https://github.com/junegunn/fzf.git
synced 2024-09-11 14:35:26 +03:00
Make height option work under Windows (#1341)
Separate Unix & Windows code into platform specific files for light renderer
This commit is contained in:
parent
7c40a424c0
commit
7d5985baf9
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
97
src/tui/light_unix.go
Normal file
97
src/tui/light_unix.go
Normal file
@ -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
|
||||
}
|
132
src/tui/light_windows.go
Normal file
132
src/tui/light_windows.go
Normal file
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user