mirror of
https://github.com/junegunn/fzf.git
synced 2025-07-31 04:02:01 -07:00
Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a71c471405 | ||
|
3858086047 | ||
|
dffef3d9f3 | ||
|
de1c6b8727 | ||
|
6f17f412ba | ||
|
746961bf43 | ||
|
182a6d99fd | ||
|
af31088481 | ||
|
43425158f4 | ||
|
8524ea7441 | ||
|
6a65006f55 | ||
|
d75ed841a9 | ||
|
3cd2547e91 | ||
|
8c661d4e8c | ||
|
4b332d831e | ||
|
22487810ba | ||
|
c49e65d926 | ||
|
2e8814bb57 | ||
|
dc557c0d4c | ||
|
a2beb159f1 | ||
|
7ce427ff47 |
1
.github/ISSUE_TEMPLATE.md
vendored
1
.github/ISSUE_TEMPLATE.md
vendored
@@ -11,6 +11,7 @@
|
||||
- [ ] Linux
|
||||
- [ ] Mac OS X
|
||||
- [ ] Windows
|
||||
- [ ] Windows Subsystem for Linux
|
||||
- [ ] Etc.
|
||||
- Shell
|
||||
- [ ] bash
|
||||
|
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,6 +1,32 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.15.9
|
||||
------
|
||||
- Fixed rendering glitches introduced in 0.15.8
|
||||
- The default escape delay is reduced to 50ms and is configurable via
|
||||
`$ESCDELAY`
|
||||
- Scroll indicator at the top-right corner of the preview window is always
|
||||
displayed when there's overflow
|
||||
- Can now be built with ncurses 6 or tcell to support extra features
|
||||
- *ncurses 6*
|
||||
- Supports more than 256 color pairs
|
||||
- Supports italics
|
||||
- *tcell*
|
||||
- 24-bit color support
|
||||
- See https://github.com/junegunn/fzf/blob/master/src/README.md#build
|
||||
|
||||
0.15.8
|
||||
------
|
||||
- Updated ANSI processor to handle more VT-100 escape sequences
|
||||
- Added `--no-bold` (and `--bold`) option
|
||||
- Improved escape sequence processing for WSL
|
||||
- Added support for `alt-[0-9]`, `f11`, and `f12` for `--bind` and `--expect`
|
||||
|
||||
0.15.7
|
||||
------
|
||||
- Fixed panic when color is disabled and header lines contain ANSI colors
|
||||
|
||||
0.15.6
|
||||
------
|
||||
- Windows binaries! (@kelleyma49)
|
||||
|
@@ -209,6 +209,8 @@ If you use vi mode on bash, you need to add `set -o vi` *before* `source
|
||||
~/.fzf.bash` in your .bashrc, so that it correctly sets up key bindings for vi
|
||||
mode.
|
||||
|
||||
More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings).
|
||||
|
||||
Fuzzy completion for bash and zsh
|
||||
---------------------------------
|
||||
|
||||
|
4
install
4
install
@@ -2,8 +2,8 @@
|
||||
|
||||
set -u
|
||||
|
||||
[[ "$@" =~ --pre ]] && version=0.15.6 pre=1 ||
|
||||
version=0.15.6 pre=0
|
||||
[[ "$@" =~ --pre ]] && version=0.15.9 pre=1 ||
|
||||
version=0.15.9 pre=0
|
||||
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
|
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf-tmux 1 "Nov 2016" "fzf 0.15.6" "fzf-tmux - open fzf in tmux split pane"
|
||||
.TH fzf-tmux 1 "Nov 2016" "fzf 0.15.9" "fzf-tmux - open fzf in tmux split pane"
|
||||
|
||||
.SH NAME
|
||||
fzf-tmux - open fzf in tmux split pane
|
||||
|
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Nov 2016" "fzf 0.15.6" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Nov 2016" "fzf 0.15.9" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -216,6 +216,9 @@ e.g. \fBfzf --color=bg+:24\fR
|
||||
\fBheader \fRHeader
|
||||
.RE
|
||||
.TP
|
||||
.B "--no-bold"
|
||||
Do not use bold text
|
||||
.TP
|
||||
.B "--black"
|
||||
Use black background
|
||||
.SS History
|
||||
@@ -388,7 +391,8 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
||||
.B AVAILABLE KEYS: (SYNONYMS)
|
||||
\fIctrl-[a-z]\fR
|
||||
\fIalt-[a-z]\fR
|
||||
\fIf[1-10]\fR
|
||||
\fIalt-[0-9]\fR
|
||||
\fIf[1-12]\fR
|
||||
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
||||
\fIspace\fR
|
||||
\fIbspace\fR (\fIbs\fR)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Key bindings
|
||||
# ------------
|
||||
__fzf_select__() {
|
||||
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
|
||||
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||
-o -type f -print \
|
||||
-o -type d -print \
|
||||
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
|
||||
@@ -41,7 +41,7 @@ fzf-file-widget() {
|
||||
|
||||
__fzf_cd__() {
|
||||
local cmd dir
|
||||
cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
|
||||
cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"}"
|
||||
dir=$(eval "$cmd | $(__fzfcmd) +m $FZF_ALT_C_OPTS") && printf 'cd %q' "$dir"
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@ function fzf_key_bindings
|
||||
|
||||
function fzf-file-widget
|
||||
set -q FZF_CTRL_T_COMMAND; or set -l FZF_CTRL_T_COMMAND "
|
||||
command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
|
||||
command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||
-o -type f -print \
|
||||
-o -type d -print \
|
||||
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"
|
||||
@@ -34,7 +34,7 @@ function fzf_key_bindings
|
||||
|
||||
function fzf-cd-widget
|
||||
set -q FZF_ALT_C_COMMAND; or set -l FZF_ALT_C_COMMAND "
|
||||
command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
|
||||
command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"
|
||||
# Fish hangs if the command before pipe redirects (2> /dev/null)
|
||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)" +m $FZF_ALT_C_OPTS > $TMPDIR/fzf.result"
|
||||
|
@@ -4,7 +4,7 @@ if [[ $- == *i* ]]; then
|
||||
|
||||
# CTRL-T - Paste the selected file path(s) into the command line
|
||||
__fsel() {
|
||||
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
|
||||
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||
-o -type f -print \
|
||||
-o -type d -print \
|
||||
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
|
||||
@@ -33,7 +33,7 @@ bindkey '^T' fzf-file-widget
|
||||
|
||||
# ALT-C - cd into the selected directory
|
||||
fzf-cd-widget() {
|
||||
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
|
||||
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"}"
|
||||
setopt localoptions pipefail 2> /dev/null
|
||||
cd "${$(eval "$cmd | $(__fzfcmd) +m $FZF_ALT_C_OPTS"):-.}"
|
||||
|
@@ -70,10 +70,10 @@ clean:
|
||||
cd fzf && rm -f fzf-*
|
||||
|
||||
fzf/$(BINARY32): deps
|
||||
cd fzf && GOARCH=386 CGO_ENABLED=1 go build -a -ldflags -w -tags "$(TAGS)" -o $(BINARY32)
|
||||
cd fzf && GOARCH=386 CGO_ENABLED=1 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARY32)
|
||||
|
||||
fzf/$(BINARY64): deps
|
||||
cd fzf && go build -a -ldflags -w -tags "$(TAGS)" -o $(BINARY64)
|
||||
cd fzf && go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARY64)
|
||||
|
||||
$(BINDIR)/fzf: fzf/$(BINARY) | $(BINDIR)
|
||||
cp -f fzf/$(BINARY) $(BINDIR)
|
||||
|
@@ -61,6 +61,45 @@ make install
|
||||
make linux
|
||||
```
|
||||
|
||||
### With ncurses 6
|
||||
|
||||
The official binaries of fzf are built with ncurses 5 because it's widely
|
||||
supported by different platforms. However ncurses 5 is old and has a number of
|
||||
limitations.
|
||||
|
||||
1. Does not support more than 256 color pairs (See [357][357])
|
||||
2. Does not support italics
|
||||
3. Does not support 24-bit color
|
||||
|
||||
[357]: https://github.com/junegunn/fzf/issues/357
|
||||
|
||||
But you can manually build fzf with ncurses 6 to overcome some of these
|
||||
limitations. ncurses 6 supports up to 32767 color pairs (1), and supports
|
||||
italics (2). To build fzf with ncurses 6, you have to install it first. On
|
||||
macOS, you can use Homebrew to install it.
|
||||
|
||||
```sh
|
||||
brew install homebrew/dupes/ncurses
|
||||
LDFLAGS="-L/usr/local/opt/ncurses/lib" make install
|
||||
```
|
||||
|
||||
### With tcell
|
||||
|
||||
[tcell][tcell] is a portable alternative to ncurses and we currently use it to
|
||||
build Windows binaries. tcell has many benefits but most importantly, it
|
||||
supports 24-bit colors. To build fzf with tcell:
|
||||
|
||||
```sh
|
||||
TAGS=tcell make install
|
||||
```
|
||||
|
||||
However, note that tcell has its own issues.
|
||||
|
||||
- Poor rendering performance compared to ncurses
|
||||
- Does not support bracketed-paste mode
|
||||
- Does not support italics unlike ncurses 6
|
||||
- Some wide characters are not correctly displayed
|
||||
|
||||
Test
|
||||
----
|
||||
|
||||
@@ -88,6 +127,8 @@ Third-party libraries used
|
||||
- Licensed under [MIT](http://mattn.mit-license.org)
|
||||
- [mattn/go-isatty](https://github.com/mattn/go-isatty)
|
||||
- Licensed under [MIT](http://mattn.mit-license.org)
|
||||
- [tcell](https://github.com/gdamore/tcell)
|
||||
- Licensed under [Apache License 2.0](https://github.com/gdamore/tcell/blob/master/LICENSE)
|
||||
|
||||
License
|
||||
-------
|
||||
@@ -99,4 +140,4 @@ License
|
||||
[gil]: http://en.wikipedia.org/wiki/Global_Interpreter_Lock
|
||||
[ncurses]: https://www.gnu.org/software/ncurses/
|
||||
[req]: http://golang.org/doc/install
|
||||
[termbox]: https://github.com/nsf/termbox-go
|
||||
[tcell]: https://github.com/gdamore/tcell
|
||||
|
39
src/ansi.go
39
src/ansi.go
@@ -35,7 +35,16 @@ func (s *ansiState) equals(t *ansiState) bool {
|
||||
var ansiRegex *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
ansiRegex = regexp.MustCompile("\x1b\\[[0-9;]*.|[\x0e\x0f]")
|
||||
/*
|
||||
References:
|
||||
- https://github.com/gnachman/iTerm2
|
||||
- http://ascii-table.com/ansi-escape-sequences.php
|
||||
- http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
||||
- http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
||||
*/
|
||||
// The following regular expression will include not all but most of the
|
||||
// frequently used ANSI sequences
|
||||
ansiRegex = regexp.MustCompile("\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b.|[\x08\x0e\x0f]")
|
||||
}
|
||||
|
||||
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
||||
@@ -100,7 +109,7 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||
} else {
|
||||
state = &ansiState{prevState.fg, prevState.bg, prevState.attr}
|
||||
}
|
||||
if ansiCode[0] != '\x1b' || ansiCode[len(ansiCode)-1] != 'm' {
|
||||
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -134,15 +143,17 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||
case 49:
|
||||
state.bg = -1
|
||||
case 1:
|
||||
state.attr = tui.Bold
|
||||
state.attr = state.attr | tui.Bold
|
||||
case 2:
|
||||
state.attr = tui.Dim
|
||||
state.attr = state.attr | tui.Dim
|
||||
case 3:
|
||||
state.attr = state.attr | tui.Italic
|
||||
case 4:
|
||||
state.attr = tui.Underline
|
||||
state.attr = state.attr | tui.Underline
|
||||
case 5:
|
||||
state.attr = tui.Blink
|
||||
state.attr = state.attr | tui.Blink
|
||||
case 7:
|
||||
state.attr = tui.Reverse
|
||||
state.attr = state.attr | tui.Reverse
|
||||
case 0:
|
||||
init()
|
||||
default:
|
||||
@@ -158,6 +169,8 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||
}
|
||||
case 1:
|
||||
switch num {
|
||||
case 2:
|
||||
state256 = 10 // MAGIC
|
||||
case 5:
|
||||
state256++
|
||||
default:
|
||||
@@ -166,8 +179,20 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||
case 2:
|
||||
*ptr = tui.Color(num)
|
||||
state256 = 0
|
||||
case 10:
|
||||
*ptr = tui.Color(1<<24) | tui.Color(num<<16)
|
||||
state256++
|
||||
case 11:
|
||||
*ptr = *ptr | tui.Color(num<<8)
|
||||
state256++
|
||||
case 12:
|
||||
*ptr = *ptr | tui.Color(num)
|
||||
state256 = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if state256 > 0 {
|
||||
*ptr = -1
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ func TestExtractColor(t *testing.T) {
|
||||
output, ansiOffsets, newState := extractColor(src, state, nil)
|
||||
state = newState
|
||||
if output != "hello world" {
|
||||
t.Errorf("Invalid output: {}", output)
|
||||
t.Errorf("Invalid output: %s %s", output, []rune(output))
|
||||
}
|
||||
fmt.Println(src, ansiOffsets, clean)
|
||||
assertion(ansiOffsets, state)
|
||||
@@ -56,7 +56,7 @@ func TestExtractColor(t *testing.T) {
|
||||
})
|
||||
|
||||
state = nil
|
||||
src = "\x1b[1mhello \x1b[mworld"
|
||||
src = "\x1b[1mhello \x1b[mw\x1b7o\x1b8r\x1b(Bl\x1b[2@d"
|
||||
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||
if len(*offsets) != 1 {
|
||||
t.Fail()
|
||||
|
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
const (
|
||||
// Current version
|
||||
version = "0.15.6"
|
||||
version = "0.15.9"
|
||||
|
||||
// Core
|
||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||
|
@@ -57,6 +57,7 @@ const usage = `usage: fzf [options]
|
||||
--ansi Enable processing of ANSI color codes
|
||||
--tabstop=SPACES Number of spaces for a tab character (default: 8)
|
||||
--color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors
|
||||
--no-bold Do not use bold text
|
||||
|
||||
History
|
||||
--history=FILE History file
|
||||
@@ -144,6 +145,7 @@ type Options struct {
|
||||
Mouse bool
|
||||
Theme *tui.ColorTheme
|
||||
Black bool
|
||||
Bold bool
|
||||
Reverse bool
|
||||
Cycle bool
|
||||
Hscroll bool
|
||||
@@ -189,6 +191,7 @@ func defaultOptions() *Options {
|
||||
Mouse: true,
|
||||
Theme: tui.EmptyTheme(),
|
||||
Black: false,
|
||||
Bold: true,
|
||||
Reverse: false,
|
||||
Cycle: false,
|
||||
Hscroll: true,
|
||||
@@ -327,6 +330,10 @@ func isAlphabet(char uint8) bool {
|
||||
return char >= 'a' && char <= 'z'
|
||||
}
|
||||
|
||||
func isNumeric(char uint8) bool {
|
||||
return char >= '0' && char <= '9'
|
||||
}
|
||||
|
||||
func parseAlgo(str string) algo.Algo {
|
||||
switch str {
|
||||
case "v1":
|
||||
@@ -403,11 +410,17 @@ func parseKeyChords(str string, message string) map[int]string {
|
||||
chord = tui.DoubleClick
|
||||
case "f10":
|
||||
chord = tui.F10
|
||||
case "f11":
|
||||
chord = tui.F11
|
||||
case "f12":
|
||||
chord = tui.F12
|
||||
default:
|
||||
if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
||||
chord = tui.CtrlA + int(lkey[5]) - 'a'
|
||||
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
|
||||
chord = tui.AltA + int(lkey[4]) - 'a'
|
||||
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isNumeric(lkey[4]) {
|
||||
chord = tui.Alt0 + int(lkey[4]) - '0'
|
||||
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
|
||||
chord = tui.F1 + int(key[1]) - '1'
|
||||
} else if utf8.RuneCountInString(key) == 1 {
|
||||
@@ -910,6 +923,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.Black = true
|
||||
case "--no-black":
|
||||
opts.Black = false
|
||||
case "--bold":
|
||||
opts.Bold = true
|
||||
case "--no-bold":
|
||||
opts.Bold = false
|
||||
case "--reverse":
|
||||
opts.Reverse = true
|
||||
case "--no-reverse":
|
||||
|
@@ -147,19 +147,21 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
||||
} else {
|
||||
ansi := itemColors[curr-1]
|
||||
fg := ansi.color.fg
|
||||
if fg == -1 {
|
||||
if current {
|
||||
fg = theme.Current
|
||||
} else {
|
||||
fg = theme.Fg
|
||||
}
|
||||
}
|
||||
bg := ansi.color.bg
|
||||
if bg == -1 {
|
||||
if current {
|
||||
bg = theme.DarkBg
|
||||
} else {
|
||||
bg = theme.Bg
|
||||
if theme != nil {
|
||||
if fg == -1 {
|
||||
if current {
|
||||
fg = theme.Current
|
||||
} else {
|
||||
fg = theme.Fg
|
||||
}
|
||||
}
|
||||
if bg == -1 {
|
||||
if current {
|
||||
bg = theme.DarkBg
|
||||
} else {
|
||||
bg = theme.Bg
|
||||
}
|
||||
}
|
||||
}
|
||||
colors = append(colors, colorOffset{
|
||||
|
@@ -70,6 +70,7 @@ type Terminal struct {
|
||||
header0 []string
|
||||
ansi bool
|
||||
margin [4]sizeSpec
|
||||
strong tui.Attr
|
||||
window *tui.Window
|
||||
bwindow *tui.Window
|
||||
pwindow *tui.Window
|
||||
@@ -189,6 +190,7 @@ const (
|
||||
func defaultKeymap() map[int]actionType {
|
||||
keymap := make(map[int]actionType)
|
||||
keymap[tui.Invalid] = actInvalid
|
||||
keymap[tui.Resize] = actClearScreen
|
||||
keymap[tui.CtrlA] = actBeginningOfLine
|
||||
keymap[tui.CtrlB] = actBackwardChar
|
||||
keymap[tui.CtrlC] = actAbort
|
||||
@@ -256,6 +258,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
if len(opts.Preview.command) > 0 {
|
||||
previewBox = util.NewEventBox()
|
||||
}
|
||||
strongAttr := tui.Bold
|
||||
if !opts.Bold {
|
||||
strongAttr = tui.AttrRegular
|
||||
}
|
||||
return &Terminal{
|
||||
initDelay: delay,
|
||||
inlineInfo: opts.InlineInfo,
|
||||
@@ -279,6 +285,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
printQuery: opts.PrintQuery,
|
||||
history: opts.History,
|
||||
margin: opts.Margin,
|
||||
strong: strongAttr,
|
||||
cycle: opts.Cycle,
|
||||
header: header,
|
||||
header0: header,
|
||||
@@ -532,24 +539,24 @@ func (t *Terminal) placeCursor() {
|
||||
|
||||
func (t *Terminal) printPrompt() {
|
||||
t.move(0, 0, true)
|
||||
t.window.CPrint(tui.ColPrompt, tui.Bold, t.prompt)
|
||||
t.window.CPrint(tui.ColNormal, tui.Bold, string(t.input))
|
||||
t.window.CPrint(tui.ColPrompt, t.strong, t.prompt)
|
||||
t.window.CPrint(tui.ColNormal, t.strong, string(t.input))
|
||||
}
|
||||
|
||||
func (t *Terminal) printInfo() {
|
||||
if t.inlineInfo {
|
||||
t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input)+1, true)
|
||||
if t.reading {
|
||||
t.window.CPrint(tui.ColSpinner, tui.Bold, " < ")
|
||||
t.window.CPrint(tui.ColSpinner, t.strong, " < ")
|
||||
} else {
|
||||
t.window.CPrint(tui.ColPrompt, tui.Bold, " < ")
|
||||
t.window.CPrint(tui.ColPrompt, t.strong, " < ")
|
||||
}
|
||||
} else {
|
||||
t.move(1, 0, true)
|
||||
if t.reading {
|
||||
duration := int64(spinnerDuration)
|
||||
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
|
||||
t.window.CPrint(tui.ColSpinner, tui.Bold, _spinner[idx])
|
||||
t.window.CPrint(tui.ColSpinner, t.strong, _spinner[idx])
|
||||
}
|
||||
t.move(1, 2, false)
|
||||
}
|
||||
@@ -627,17 +634,17 @@ func (t *Terminal) printItem(result *Result, i int, current bool) {
|
||||
} else if current {
|
||||
label = ">"
|
||||
}
|
||||
t.window.CPrint(tui.ColCursor, tui.Bold, label)
|
||||
t.window.CPrint(tui.ColCursor, t.strong, label)
|
||||
if current {
|
||||
if selected {
|
||||
t.window.CPrint(tui.ColSelected, tui.Bold, ">")
|
||||
t.window.CPrint(tui.ColSelected, t.strong, ">")
|
||||
} else {
|
||||
t.window.CPrint(tui.ColCurrent, tui.Bold, " ")
|
||||
t.window.CPrint(tui.ColCurrent, t.strong, " ")
|
||||
}
|
||||
t.printHighlighted(result, tui.Bold, tui.ColCurrent, tui.ColCurrentMatch, true)
|
||||
t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true)
|
||||
} else {
|
||||
if selected {
|
||||
t.window.CPrint(tui.ColSelected, tui.Bold, ">")
|
||||
t.window.CPrint(tui.ColSelected, t.strong, ">")
|
||||
} else {
|
||||
t.window.Print(" ")
|
||||
}
|
||||
@@ -826,7 +833,7 @@ func (t *Terminal) printPreview() {
|
||||
}
|
||||
return t.pwindow.Fill(str)
|
||||
})
|
||||
if t.previewer.offset > 0 {
|
||||
if t.previewer.lines > t.pwindow.Height {
|
||||
offset := fmt.Sprintf("%d/%d", t.previewer.offset+1, t.previewer.lines)
|
||||
t.pwindow.Move(0, t.pwindow.Width-len(offset))
|
||||
t.pwindow.CPrint(tui.ColInfo, tui.Reverse, offset)
|
||||
|
@@ -10,8 +10,12 @@ package tui
|
||||
#cgo static LDFLAGS: -l:libncursesw.a -l:libtinfo.a -l:libgpm.a -ldl
|
||||
#cgo android static LDFLAGS: -l:libncurses.a -fPIE -march=armv7-a -mfpu=neon -mhard-float -Wl,--no-warn-mismatch
|
||||
|
||||
SCREEN *c_newterm () {
|
||||
return newterm(NULL, stderr, stdin);
|
||||
FILE* c_tty() {
|
||||
return fopen("/dev/tty", "r");
|
||||
}
|
||||
|
||||
SCREEN* c_newterm(FILE* tty) {
|
||||
return newterm(NULL, stderr, tty);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
@@ -19,24 +23,26 @@ import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type ColorPair int16
|
||||
type Attr C.int
|
||||
type Attr C.uint
|
||||
type WindowImpl C.WINDOW
|
||||
|
||||
const (
|
||||
Bold = C.A_BOLD
|
||||
Dim = C.A_DIM
|
||||
Blink = C.A_BLINK
|
||||
Reverse = C.A_REVERSE
|
||||
Underline = C.A_UNDERLINE
|
||||
Bold Attr = C.A_BOLD
|
||||
Dim = C.A_DIM
|
||||
Blink = C.A_BLINK
|
||||
Reverse = C.A_REVERSE
|
||||
Underline = C.A_UNDERLINE
|
||||
)
|
||||
|
||||
var Italic Attr = C.A_VERTICAL << 1 // FIXME
|
||||
|
||||
const (
|
||||
AttrRegular Attr = 0
|
||||
)
|
||||
@@ -59,14 +65,16 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
_in *os.File
|
||||
_screen *C.SCREEN
|
||||
_colorMap map[int]ColorPair
|
||||
_colorFn func(ColorPair, Attr) C.int
|
||||
_colorFn func(ColorPair, Attr) (C.short, C.int)
|
||||
)
|
||||
|
||||
func init() {
|
||||
_colorMap = make(map[int]ColorPair)
|
||||
if strings.HasPrefix(C.GoString(C.curses_version()), "ncurses 5") {
|
||||
Italic = C.A_NORMAL
|
||||
}
|
||||
}
|
||||
|
||||
func (a Attr) Merge(b Attr) Attr {
|
||||
@@ -81,18 +89,13 @@ func DefaultTheme() *ColorTheme {
|
||||
}
|
||||
|
||||
func Init(theme *ColorTheme, black bool, mouse bool) {
|
||||
{
|
||||
in, err := os.OpenFile("/dev/tty", syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
panic("Failed to open /dev/tty")
|
||||
}
|
||||
_in = in
|
||||
// Break STDIN
|
||||
// syscall.Dup2(int(in.Fd()), int(os.Stdin.Fd()))
|
||||
}
|
||||
|
||||
C.setlocale(C.LC_ALL, C.CString(""))
|
||||
_screen = C.c_newterm()
|
||||
tty := C.c_tty()
|
||||
if tty == nil {
|
||||
fmt.Println("Failed to open /dev/tty")
|
||||
os.Exit(2)
|
||||
}
|
||||
_screen = C.c_newterm(tty)
|
||||
if _screen == nil {
|
||||
fmt.Println("Invalid $TERM: " + os.Getenv("TERM"))
|
||||
os.Exit(2)
|
||||
@@ -100,9 +103,22 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
|
||||
C.set_term(_screen)
|
||||
if mouse {
|
||||
C.mousemask(C.ALL_MOUSE_EVENTS, nil)
|
||||
C.mouseinterval(0)
|
||||
}
|
||||
C.noecho()
|
||||
C.raw() // stty dsusp undef
|
||||
C.nonl()
|
||||
C.keypad(C.stdscr, true)
|
||||
|
||||
delay := 50
|
||||
delayEnv := os.Getenv("ESCDELAY")
|
||||
if len(delayEnv) > 0 {
|
||||
num, err := strconv.Atoi(delayEnv)
|
||||
if err == nil && num >= 0 {
|
||||
delay = num
|
||||
}
|
||||
}
|
||||
C.set_escdelay(C.int(delay))
|
||||
|
||||
_color = theme != nil
|
||||
if _color {
|
||||
@@ -114,6 +130,13 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
|
||||
} else {
|
||||
_colorFn = attrMono
|
||||
}
|
||||
|
||||
C.nodelay(C.stdscr, true)
|
||||
ch := C.getch()
|
||||
if ch != C.ERR {
|
||||
C.ungetch(ch)
|
||||
}
|
||||
C.nodelay(C.stdscr, false)
|
||||
}
|
||||
|
||||
func initPairs(theme *ColorTheme) {
|
||||
@@ -153,10 +176,12 @@ func NewWindow(top int, left int, width int, height int, border bool) *Window {
|
||||
C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal))))
|
||||
}
|
||||
if border {
|
||||
attr := _colorFn(ColBorder, 0)
|
||||
pair, attr := _colorFn(ColBorder, 0)
|
||||
C.wcolor_set(win, pair, nil)
|
||||
C.wattron(win, attr)
|
||||
C.box(win, 0, 0)
|
||||
C.wattroff(win, attr)
|
||||
C.wcolor_set(win, 0, nil)
|
||||
}
|
||||
|
||||
return &Window{
|
||||
@@ -168,21 +193,15 @@ func NewWindow(top int, left int, width int, height int, border bool) *Window {
|
||||
}
|
||||
}
|
||||
|
||||
func attrColored(pair ColorPair, a Attr) C.int {
|
||||
var attr C.int
|
||||
if pair > 0 {
|
||||
attr = C.COLOR_PAIR(C.int(pair))
|
||||
}
|
||||
return attr | C.int(a)
|
||||
func attrColored(pair ColorPair, a Attr) (C.short, C.int) {
|
||||
return C.short(pair), C.int(a)
|
||||
}
|
||||
|
||||
func attrMono(pair ColorPair, a Attr) C.int {
|
||||
func attrMono(pair ColorPair, a Attr) (C.short, C.int) {
|
||||
var attr C.int
|
||||
switch pair {
|
||||
case ColCurrent:
|
||||
if C.int(a)&C.A_BOLD == C.A_BOLD {
|
||||
attr = C.A_REVERSE
|
||||
}
|
||||
attr = C.A_REVERSE
|
||||
case ColMatch:
|
||||
attr = C.A_UNDERLINE
|
||||
case ColCurrentMatch:
|
||||
@@ -191,7 +210,7 @@ func attrMono(pair ColorPair, a Attr) C.int {
|
||||
if C.int(a)&C.A_BOLD == C.A_BOLD {
|
||||
attr = attr | C.A_BOLD
|
||||
}
|
||||
return attr
|
||||
return 0, attr
|
||||
}
|
||||
|
||||
func MaxX() int {
|
||||
@@ -232,11 +251,13 @@ func (w *Window) Print(text string) {
|
||||
}, text)))
|
||||
}
|
||||
|
||||
func (w *Window) CPrint(pair ColorPair, a Attr, text string) {
|
||||
attr := _colorFn(pair, a)
|
||||
C.wattron(w.win(), attr)
|
||||
func (w *Window) CPrint(pair ColorPair, attr Attr, text string) {
|
||||
p, a := _colorFn(pair, attr)
|
||||
C.wcolor_set(w.win(), p, nil)
|
||||
C.wattron(w.win(), a)
|
||||
w.Print(text)
|
||||
C.wattroff(w.win(), attr)
|
||||
C.wattroff(w.win(), a)
|
||||
C.wcolor_set(w.win(), 0, nil)
|
||||
}
|
||||
|
||||
func Clear() {
|
||||
@@ -256,11 +277,13 @@ func (w *Window) Fill(str string) bool {
|
||||
return C.waddstr(w.win(), C.CString(str)) == C.OK
|
||||
}
|
||||
|
||||
func (w *Window) CFill(str string, fg Color, bg Color, a Attr) bool {
|
||||
attr := _colorFn(PairFor(fg, bg), a)
|
||||
C.wattron(w.win(), attr)
|
||||
func (w *Window) CFill(str string, fg Color, bg Color, attr Attr) bool {
|
||||
pair := PairFor(fg, bg)
|
||||
C.wcolor_set(w.win(), C.short(pair), nil)
|
||||
C.wattron(w.win(), C.int(attr))
|
||||
ret := w.Fill(str)
|
||||
C.wattroff(w.win(), attr)
|
||||
C.wattroff(w.win(), C.int(attr))
|
||||
C.wcolor_set(w.win(), 0, nil)
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -272,6 +295,10 @@ func RefreshWindows(windows []*Window) {
|
||||
}
|
||||
|
||||
func PairFor(fg Color, bg Color) ColorPair {
|
||||
// ncurses does not support 24-bit colors
|
||||
if fg.is24() || bg.is24() {
|
||||
return ColDefault
|
||||
}
|
||||
key := (int(fg) << 8) + int(bg)
|
||||
if found, prs := _colorMap[key]; prs {
|
||||
return found
|
||||
@@ -283,246 +310,169 @@ func PairFor(fg Color, bg Color) ColorPair {
|
||||
return id
|
||||
}
|
||||
|
||||
func getch(nonblock bool) int {
|
||||
b := make([]byte, 1)
|
||||
syscall.SetNonblock(int(_in.Fd()), nonblock)
|
||||
_, err := _in.Read(b)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return int(b[0])
|
||||
}
|
||||
|
||||
func GetBytes() []byte {
|
||||
c := getch(false)
|
||||
retries := 0
|
||||
if c == 27 {
|
||||
// Wait for additional keys after ESC for 100ms (10 * 10ms)
|
||||
retries = 10
|
||||
}
|
||||
_buf = append(_buf, byte(c))
|
||||
|
||||
for {
|
||||
c = getch(true)
|
||||
if c == -1 {
|
||||
if retries > 0 {
|
||||
retries--
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
break
|
||||
func consume(expects ...rune) bool {
|
||||
for _, r := range expects {
|
||||
if int(C.getch()) != int(r) {
|
||||
return false
|
||||
}
|
||||
retries = 0
|
||||
_buf = append(_buf, byte(c))
|
||||
}
|
||||
|
||||
return _buf
|
||||
return true
|
||||
}
|
||||
|
||||
// 27 (91 79) 77 type x y
|
||||
func mouseSequence(sz *int) Event {
|
||||
if len(_buf) < 6 {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 6
|
||||
switch _buf[3] {
|
||||
case 32, 36, 40, 48, // mouse-down / shift / cmd / ctrl
|
||||
35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
|
||||
mod := _buf[3] >= 36
|
||||
down := _buf[3]%2 == 0
|
||||
x := int(_buf[4] - 33)
|
||||
y := int(_buf[5] - 33)
|
||||
double := false
|
||||
if down {
|
||||
now := time.Now()
|
||||
if now.Sub(_prevDownTime) < doubleClickDuration {
|
||||
_clickY = append(_clickY, y)
|
||||
} else {
|
||||
_clickY = []int{y}
|
||||
}
|
||||
_prevDownTime = now
|
||||
} else {
|
||||
if len(_clickY) > 1 && _clickY[0] == _clickY[1] &&
|
||||
time.Now().Sub(_prevDownTime) < doubleClickDuration {
|
||||
double = true
|
||||
}
|
||||
}
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, down, double, mod}}
|
||||
case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
|
||||
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
|
||||
mod := _buf[3] >= 100
|
||||
s := 1 - int(_buf[3]%2)*2
|
||||
x := int(_buf[4] - 33)
|
||||
y := int(_buf[5] - 33)
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, mod}}
|
||||
}
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
|
||||
func escSequence(sz *int) Event {
|
||||
if len(_buf) < 2 {
|
||||
func escSequence() Event {
|
||||
C.nodelay(C.stdscr, true)
|
||||
defer func() {
|
||||
C.nodelay(C.stdscr, false)
|
||||
}()
|
||||
c := C.getch()
|
||||
switch c {
|
||||
case C.ERR:
|
||||
return Event{ESC, 0, nil}
|
||||
}
|
||||
*sz = 2
|
||||
switch _buf[1] {
|
||||
case 13:
|
||||
case CtrlM:
|
||||
return Event{AltEnter, 0, nil}
|
||||
case 32:
|
||||
return Event{AltSpace, 0, nil}
|
||||
case 47:
|
||||
case '/':
|
||||
return Event{AltSlash, 0, nil}
|
||||
case 98:
|
||||
return Event{AltB, 0, nil}
|
||||
case 100:
|
||||
return Event{AltD, 0, nil}
|
||||
case 102:
|
||||
return Event{AltF, 0, nil}
|
||||
case 127:
|
||||
case ' ':
|
||||
return Event{AltSpace, 0, nil}
|
||||
case 127, C.KEY_BACKSPACE:
|
||||
return Event{AltBS, 0, nil}
|
||||
case 91, 79:
|
||||
if len(_buf) < 3 {
|
||||
case '[':
|
||||
// Bracketed paste mode (printf "\e[?2004h")
|
||||
// \e[200~ TEXT \e[201~
|
||||
if consume('2', '0', '0', '~') {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 3
|
||||
switch _buf[2] {
|
||||
case 68:
|
||||
return Event{Left, 0, nil}
|
||||
case 67:
|
||||
return Event{Right, 0, nil}
|
||||
case 66:
|
||||
return Event{Down, 0, nil}
|
||||
case 65:
|
||||
return Event{Up, 0, nil}
|
||||
case 90:
|
||||
return Event{BTab, 0, nil}
|
||||
case 72:
|
||||
return Event{Home, 0, nil}
|
||||
case 70:
|
||||
return Event{End, 0, nil}
|
||||
case 77:
|
||||
return mouseSequence(sz)
|
||||
case 80:
|
||||
return Event{F1, 0, nil}
|
||||
case 81:
|
||||
return Event{F2, 0, nil}
|
||||
case 82:
|
||||
return Event{F3, 0, nil}
|
||||
case 83:
|
||||
return Event{F4, 0, nil}
|
||||
case 49, 50, 51, 52, 53, 54:
|
||||
if len(_buf) < 4 {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 4
|
||||
switch _buf[2] {
|
||||
case 50:
|
||||
if len(_buf) == 5 && _buf[4] == 126 {
|
||||
*sz = 5
|
||||
switch _buf[3] {
|
||||
case 48:
|
||||
return Event{F9, 0, nil}
|
||||
case 49:
|
||||
return Event{F10, 0, nil}
|
||||
}
|
||||
}
|
||||
// Bracketed paste mode \e[200~ / \e[201
|
||||
if _buf[3] == 48 && (_buf[4] == 48 || _buf[4] == 49) && _buf[5] == 126 {
|
||||
*sz = 6
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
return Event{Invalid, 0, nil} // INS
|
||||
case 51:
|
||||
return Event{Del, 0, nil}
|
||||
case 52:
|
||||
return Event{End, 0, nil}
|
||||
case 53:
|
||||
return Event{PgUp, 0, nil}
|
||||
case 54:
|
||||
return Event{PgDn, 0, nil}
|
||||
case 49:
|
||||
switch _buf[3] {
|
||||
case 126:
|
||||
return Event{Home, 0, nil}
|
||||
case 53, 55, 56, 57:
|
||||
if len(_buf) == 5 && _buf[4] == 126 {
|
||||
*sz = 5
|
||||
switch _buf[3] {
|
||||
case 53:
|
||||
return Event{F5, 0, nil}
|
||||
case 55:
|
||||
return Event{F6, 0, nil}
|
||||
case 56:
|
||||
return Event{F7, 0, nil}
|
||||
case 57:
|
||||
return Event{F8, 0, nil}
|
||||
}
|
||||
}
|
||||
return Event{Invalid, 0, nil}
|
||||
case 59:
|
||||
if len(_buf) != 6 {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 6
|
||||
switch _buf[4] {
|
||||
case 50:
|
||||
switch _buf[5] {
|
||||
case 68:
|
||||
return Event{Home, 0, nil}
|
||||
case 67:
|
||||
return Event{End, 0, nil}
|
||||
}
|
||||
case 53:
|
||||
switch _buf[5] {
|
||||
case 68:
|
||||
return Event{SLeft, 0, nil}
|
||||
case 67:
|
||||
return Event{SRight, 0, nil}
|
||||
}
|
||||
} // _buf[4]
|
||||
} // _buf[3]
|
||||
} // _buf[2]
|
||||
} // _buf[2]
|
||||
} // _buf[1]
|
||||
if _buf[1] >= 'a' && _buf[1] <= 'z' {
|
||||
return Event{AltA + int(_buf[1]) - 'a', 0, nil}
|
||||
}
|
||||
if c >= 'a' && c <= 'z' {
|
||||
return Event{AltA + int(c) - 'a', 0, nil}
|
||||
}
|
||||
|
||||
if c >= '0' && c <= '9' {
|
||||
return Event{Alt0 + int(c) - '0', 0, nil}
|
||||
}
|
||||
|
||||
// Don't care. Ignore the rest.
|
||||
for ; c != C.ERR; c = C.getch() {
|
||||
}
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
|
||||
func GetChar() Event {
|
||||
if len(_buf) == 0 {
|
||||
_buf = GetBytes()
|
||||
}
|
||||
if len(_buf) == 0 {
|
||||
panic("Empty _buffer")
|
||||
}
|
||||
|
||||
sz := 1
|
||||
defer func() {
|
||||
_buf = _buf[sz:]
|
||||
}()
|
||||
|
||||
switch _buf[0] {
|
||||
case CtrlC:
|
||||
return Event{CtrlC, 0, nil}
|
||||
case CtrlG:
|
||||
return Event{CtrlG, 0, nil}
|
||||
case CtrlQ:
|
||||
return Event{CtrlQ, 0, nil}
|
||||
c := C.getch()
|
||||
switch c {
|
||||
case C.ERR:
|
||||
return Event{Invalid, 0, nil}
|
||||
case C.KEY_UP:
|
||||
return Event{Up, 0, nil}
|
||||
case C.KEY_DOWN:
|
||||
return Event{Down, 0, nil}
|
||||
case C.KEY_LEFT:
|
||||
return Event{Left, 0, nil}
|
||||
case C.KEY_RIGHT:
|
||||
return Event{Right, 0, nil}
|
||||
case C.KEY_HOME:
|
||||
return Event{Home, 0, nil}
|
||||
case C.KEY_END:
|
||||
return Event{End, 0, nil}
|
||||
case C.KEY_BACKSPACE:
|
||||
return Event{BSpace, 0, nil}
|
||||
case C.KEY_F0 + 1:
|
||||
return Event{F1, 0, nil}
|
||||
case C.KEY_F0 + 2:
|
||||
return Event{F2, 0, nil}
|
||||
case C.KEY_F0 + 3:
|
||||
return Event{F3, 0, nil}
|
||||
case C.KEY_F0 + 4:
|
||||
return Event{F4, 0, nil}
|
||||
case C.KEY_F0 + 5:
|
||||
return Event{F5, 0, nil}
|
||||
case C.KEY_F0 + 6:
|
||||
return Event{F6, 0, nil}
|
||||
case C.KEY_F0 + 7:
|
||||
return Event{F7, 0, nil}
|
||||
case C.KEY_F0 + 8:
|
||||
return Event{F8, 0, nil}
|
||||
case C.KEY_F0 + 9:
|
||||
return Event{F9, 0, nil}
|
||||
case C.KEY_F0 + 10:
|
||||
return Event{F10, 0, nil}
|
||||
case C.KEY_F0 + 11:
|
||||
return Event{F11, 0, nil}
|
||||
case C.KEY_F0 + 12:
|
||||
return Event{F12, 0, nil}
|
||||
case C.KEY_DC:
|
||||
return Event{Del, 0, nil}
|
||||
case C.KEY_PPAGE:
|
||||
return Event{PgUp, 0, nil}
|
||||
case C.KEY_NPAGE:
|
||||
return Event{PgDn, 0, nil}
|
||||
case C.KEY_BTAB:
|
||||
return Event{BTab, 0, nil}
|
||||
case C.KEY_ENTER:
|
||||
return Event{CtrlM, 0, nil}
|
||||
case C.KEY_SLEFT:
|
||||
return Event{SLeft, 0, nil}
|
||||
case C.KEY_SRIGHT:
|
||||
return Event{SRight, 0, nil}
|
||||
case C.KEY_MOUSE:
|
||||
var me C.MEVENT
|
||||
if C.getmouse(&me) != C.ERR {
|
||||
mod := ((me.bstate & C.BUTTON_SHIFT) | (me.bstate & C.BUTTON_CTRL) | (me.bstate & C.BUTTON_ALT)) > 0
|
||||
x := int(me.x)
|
||||
y := int(me.y)
|
||||
/* Cannot use BUTTON1_DOUBLE_CLICKED due to mouseinterval(0) */
|
||||
if (me.bstate & C.BUTTON1_PRESSED) > 0 {
|
||||
now := time.Now()
|
||||
if now.Sub(_prevDownTime) < doubleClickDuration {
|
||||
_clickY = append(_clickY, y)
|
||||
} else {
|
||||
_clickY = []int{y}
|
||||
_prevDownTime = now
|
||||
}
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, false, mod}}
|
||||
} else if (me.bstate & C.BUTTON1_RELEASED) > 0 {
|
||||
double := false
|
||||
if len(_clickY) > 1 && _clickY[0] == _clickY[1] &&
|
||||
time.Now().Sub(_prevDownTime) < doubleClickDuration {
|
||||
double = true
|
||||
}
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, double, mod}}
|
||||
} else if (me.bstate&0x8000000) > 0 || (me.bstate&0x80) > 0 {
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, mod}}
|
||||
} else if (me.bstate & C.BUTTON4_PRESSED) > 0 {
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, 1, false, false, mod}}
|
||||
}
|
||||
}
|
||||
return Event{Invalid, 0, nil}
|
||||
case C.KEY_RESIZE:
|
||||
return Event{Resize, 0, nil}
|
||||
case ESC:
|
||||
return escSequence()
|
||||
case 127:
|
||||
return Event{BSpace, 0, nil}
|
||||
case ESC:
|
||||
return escSequence(&sz)
|
||||
}
|
||||
// CTRL-A ~ CTRL-Z
|
||||
if c >= CtrlA && c <= CtrlZ {
|
||||
return Event{int(c), 0, nil}
|
||||
}
|
||||
|
||||
// CTRL-A ~ CTRL-Z
|
||||
if _buf[0] <= CtrlZ {
|
||||
return Event{int(_buf[0]), 0, nil}
|
||||
// Multi-byte character
|
||||
buffer := []byte{byte(c)}
|
||||
for {
|
||||
r, _ := utf8.DecodeRune(buffer)
|
||||
if r != utf8.RuneError {
|
||||
return Event{Rune, r, nil}
|
||||
}
|
||||
|
||||
c := C.getch()
|
||||
if c == C.ERR {
|
||||
break
|
||||
}
|
||||
if c >= C.KEY_CODE_YES {
|
||||
C.ungetch(c)
|
||||
break
|
||||
}
|
||||
buffer = append(buffer, byte(c))
|
||||
}
|
||||
r, rsz := utf8.DecodeRune(_buf)
|
||||
if r == utf8.RuneError {
|
||||
return Event{ESC, 0, nil}
|
||||
}
|
||||
sz = rsz
|
||||
return Event{Rune, r, nil}
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
|
@@ -11,8 +11,9 @@ import (
|
||||
|
||||
"runtime"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/gdamore/tcell/encoding"
|
||||
// https://github.com/gdamore/tcell/pull/135
|
||||
"github.com/junegunn/tcell"
|
||||
"github.com/junegunn/tcell/encoding"
|
||||
|
||||
"github.com/junegunn/go-runewidth"
|
||||
)
|
||||
@@ -43,11 +44,12 @@ type WindowTcell struct {
|
||||
type WindowImpl WindowTcell
|
||||
|
||||
const (
|
||||
Bold = Attr(tcell.AttrBold)
|
||||
Dim = Attr(tcell.AttrDim)
|
||||
Blink = Attr(tcell.AttrBlink)
|
||||
Reverse = Attr(tcell.AttrReverse)
|
||||
Underline = Attr(tcell.AttrUnderline)
|
||||
Bold Attr = Attr(tcell.AttrBold)
|
||||
Dim = Attr(tcell.AttrDim)
|
||||
Blink = Attr(tcell.AttrBlink)
|
||||
Reverse = Attr(tcell.AttrReverse)
|
||||
Underline = Attr(tcell.AttrUnderline)
|
||||
Italic = Attr(tcell.AttrNone) // Not supported
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -183,7 +185,7 @@ func GetChar() Event {
|
||||
ev := _screen.PollEvent()
|
||||
switch ev := ev.(type) {
|
||||
case *tcell.EventResize:
|
||||
return Event{Invalid, 0, nil}
|
||||
return Event{Resize, 0, nil}
|
||||
|
||||
// process mouse events:
|
||||
case *tcell.EventMouse:
|
||||
@@ -207,8 +209,8 @@ func GetChar() Event {
|
||||
_clickY = append(_clickY, x)
|
||||
} else {
|
||||
_clickY = []int{x}
|
||||
_prevDownTime = now
|
||||
}
|
||||
_prevDownTime = now
|
||||
} else {
|
||||
if len(_clickY) > 1 && _clickY[0] == _clickY[1] &&
|
||||
time.Now().Sub(_prevDownTime) < doubleClickDuration {
|
||||
@@ -326,9 +328,9 @@ func GetChar() Event {
|
||||
case tcell.KeyF10:
|
||||
return Event{F10, 0, nil}
|
||||
case tcell.KeyF11:
|
||||
return Event{Invalid, 0, nil}
|
||||
return Event{F11, 0, nil}
|
||||
case tcell.KeyF12:
|
||||
return Event{Invalid, 0, nil}
|
||||
return Event{F12, 0, nil}
|
||||
|
||||
// ev.Ch doesn't work for some reason for space:
|
||||
case tcell.KeyRune:
|
||||
@@ -343,6 +345,9 @@ func GetChar() Event {
|
||||
if r >= 'a' && r <= 'z' {
|
||||
return Event{AltA + int(r) - 'a', 0, nil}
|
||||
}
|
||||
if r >= '0' && r <= '9' {
|
||||
return Event{Alt0 + int(r) - '0', 0, nil}
|
||||
}
|
||||
}
|
||||
return Event{Rune, r, nil}
|
||||
|
||||
|
@@ -37,6 +37,7 @@ const (
|
||||
ESC
|
||||
|
||||
Invalid
|
||||
Resize
|
||||
Mouse
|
||||
DoubleClick
|
||||
|
||||
@@ -67,18 +68,24 @@ const (
|
||||
F8
|
||||
F9
|
||||
F10
|
||||
F11
|
||||
F12
|
||||
|
||||
AltEnter
|
||||
AltSpace
|
||||
AltSlash
|
||||
AltBS
|
||||
AltA
|
||||
|
||||
Alt0
|
||||
)
|
||||
|
||||
const ( // Reset iota
|
||||
AltA = Alt0 + 'a' - '0' + iota
|
||||
AltB
|
||||
AltC
|
||||
AltD
|
||||
AltE
|
||||
AltF
|
||||
|
||||
AltZ = AltA + 'z' - 'a'
|
||||
)
|
||||
|
||||
@@ -86,7 +93,11 @@ const (
|
||||
doubleClickDuration = 500 * time.Millisecond
|
||||
)
|
||||
|
||||
type Color int16
|
||||
type Color int32
|
||||
|
||||
func (c Color) is24() bool {
|
||||
return c > 0 && (c&(1<<24)) > 0
|
||||
}
|
||||
|
||||
const (
|
||||
colUndefined Color = -2
|
||||
@@ -136,7 +147,6 @@ type MouseEvent struct {
|
||||
}
|
||||
|
||||
var (
|
||||
_buf []byte
|
||||
_color bool
|
||||
_prevDownTime time.Time
|
||||
_clickY []int
|
||||
|
Reference in New Issue
Block a user