parent
43e8efe895
commit
f413a3dbb2
|
@ -55,3 +55,5 @@ func (n noopMode) ApplyMode() error {
|
||||||
func TerminalMode() (ModeApplier, error) {
|
func TerminalMode() (ModeApplier, error) {
|
||||||
return noopMode{}, nil
|
return noopMode{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cursorColumn = true
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
module github.com/peterh/liner
|
||||||
|
|
||||||
|
require github.com/mattn/go-runewidth v0.0.3
|
|
@ -0,0 +1,2 @@
|
||||||
|
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
|
||||||
|
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
|
@ -264,9 +264,9 @@ func (s *State) readNext() (interface{}, error) {
|
||||||
return pageUp, nil
|
return pageUp, nil
|
||||||
case 6:
|
case 6:
|
||||||
return pageDown, nil
|
return pageDown, nil
|
||||||
case 7:
|
case 1, 7:
|
||||||
return home, nil
|
return home, nil
|
||||||
case 8:
|
case 4, 8:
|
||||||
return end, nil
|
return end, nil
|
||||||
case 15:
|
case 15:
|
||||||
return f5, nil
|
return f5, nil
|
||||||
|
@ -328,6 +328,9 @@ func (s *State) readNext() (interface{}, error) {
|
||||||
case 'b':
|
case 'b':
|
||||||
s.pending = s.pending[:0] // escape code complete
|
s.pending = s.pending[:0] // escape code complete
|
||||||
return altB, nil
|
return altB, nil
|
||||||
|
case 'd':
|
||||||
|
s.pending = s.pending[:0] // escape code complete
|
||||||
|
return altD, nil
|
||||||
case 'f':
|
case 'f':
|
||||||
s.pending = s.pending[:0] // escape code complete
|
s.pending = s.pending[:0] // escape code complete
|
||||||
return altF, nil
|
return altF, nil
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -103,7 +104,7 @@ type key_event_record struct {
|
||||||
RepeatCount uint16
|
RepeatCount uint16
|
||||||
VirtualKeyCode uint16
|
VirtualKeyCode uint16
|
||||||
VirtualScanCode uint16
|
VirtualScanCode uint16
|
||||||
Char int16
|
Char uint16
|
||||||
ControlKeyState uint32
|
ControlKeyState uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,6 +112,7 @@ type key_event_record struct {
|
||||||
// what golint suggests)
|
// what golint suggests)
|
||||||
const (
|
const (
|
||||||
vk_tab = 0x09
|
vk_tab = 0x09
|
||||||
|
vk_menu = 0x12 // ALT key
|
||||||
vk_prior = 0x21
|
vk_prior = 0x21
|
||||||
vk_next = 0x22
|
vk_next = 0x22
|
||||||
vk_end = 0x23
|
vk_end = 0x23
|
||||||
|
@ -134,6 +136,7 @@ const (
|
||||||
vk_f11 = 0x7a
|
vk_f11 = 0x7a
|
||||||
vk_f12 = 0x7b
|
vk_f12 = 0x7b
|
||||||
bKey = 0x42
|
bKey = 0x42
|
||||||
|
dKey = 0x44
|
||||||
fKey = 0x46
|
fKey = 0x46
|
||||||
yKey = 0x59
|
yKey = 0x59
|
||||||
)
|
)
|
||||||
|
@ -174,6 +177,8 @@ func (s *State) readNext() (interface{}, error) {
|
||||||
var rv uint32
|
var rv uint32
|
||||||
prv := uintptr(unsafe.Pointer(&rv))
|
prv := uintptr(unsafe.Pointer(&rv))
|
||||||
|
|
||||||
|
var surrogate uint16
|
||||||
|
|
||||||
for {
|
for {
|
||||||
ok, _, err := procReadConsoleInput.Call(uintptr(s.handle), pbuf, 1, prv)
|
ok, _, err := procReadConsoleInput.Call(uintptr(s.handle), pbuf, 1, prv)
|
||||||
|
|
||||||
|
@ -184,9 +189,6 @@ func (s *State) readNext() (interface{}, error) {
|
||||||
if input.eventType == window_buffer_size_event {
|
if input.eventType == window_buffer_size_event {
|
||||||
xy := (*coord)(unsafe.Pointer(&input.blob[0]))
|
xy := (*coord)(unsafe.Pointer(&input.blob[0]))
|
||||||
s.columns = int(xy.x)
|
s.columns = int(xy.x)
|
||||||
if s.columns > 1 {
|
|
||||||
s.columns--
|
|
||||||
}
|
|
||||||
return winch, nil
|
return winch, nil
|
||||||
}
|
}
|
||||||
if input.eventType != key_event {
|
if input.eventType != key_event {
|
||||||
|
@ -194,6 +196,17 @@ func (s *State) readNext() (interface{}, error) {
|
||||||
}
|
}
|
||||||
ke := (*key_event_record)(unsafe.Pointer(&input.blob[0]))
|
ke := (*key_event_record)(unsafe.Pointer(&input.blob[0]))
|
||||||
if ke.KeyDown == 0 {
|
if ke.KeyDown == 0 {
|
||||||
|
if ke.VirtualKeyCode == vk_menu && ke.Char > 0 {
|
||||||
|
// paste of unicode (eg. via ALT-numpad)
|
||||||
|
if surrogate > 0 {
|
||||||
|
return utf16.DecodeRune(rune(surrogate), rune(ke.Char)), nil
|
||||||
|
} else if utf16.IsSurrogate(rune(ke.Char)) {
|
||||||
|
surrogate = ke.Char
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return rune(ke.Char), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +215,9 @@ func (s *State) readNext() (interface{}, error) {
|
||||||
} else if ke.VirtualKeyCode == bKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
|
} else if ke.VirtualKeyCode == bKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
|
||||||
ke.ControlKeyState&modKeys == rightAltPressed) {
|
ke.ControlKeyState&modKeys == rightAltPressed) {
|
||||||
s.key = altB
|
s.key = altB
|
||||||
|
} else if ke.VirtualKeyCode == dKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
|
||||||
|
ke.ControlKeyState&modKeys == rightAltPressed) {
|
||||||
|
s.key = altD
|
||||||
} else if ke.VirtualKeyCode == fKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
|
} else if ke.VirtualKeyCode == fKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
|
||||||
ke.ControlKeyState&modKeys == rightAltPressed) {
|
ke.ControlKeyState&modKeys == rightAltPressed) {
|
||||||
s.key = altF
|
s.key = altF
|
||||||
|
@ -209,7 +225,14 @@ func (s *State) readNext() (interface{}, error) {
|
||||||
ke.ControlKeyState&modKeys == rightAltPressed) {
|
ke.ControlKeyState&modKeys == rightAltPressed) {
|
||||||
s.key = altY
|
s.key = altY
|
||||||
} else if ke.Char > 0 {
|
} else if ke.Char > 0 {
|
||||||
s.key = rune(ke.Char)
|
if surrogate > 0 {
|
||||||
|
s.key = utf16.DecodeRune(rune(surrogate), rune(ke.Char))
|
||||||
|
} else if utf16.IsSurrogate(rune(ke.Char)) {
|
||||||
|
surrogate = ke.Char
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
s.key = rune(ke.Char)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
switch ke.VirtualKeyCode {
|
switch ke.VirtualKeyCode {
|
||||||
case vk_prior:
|
case vk_prior:
|
||||||
|
@ -337,3 +360,5 @@ func TerminalMode() (ModeApplier, error) {
|
||||||
}
|
}
|
||||||
return mode, err
|
return mode, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cursorColumn = true
|
||||||
|
|
|
@ -40,6 +40,7 @@ const (
|
||||||
f11
|
f11
|
||||||
f12
|
f12
|
||||||
altB
|
altB
|
||||||
|
altD
|
||||||
altF
|
altF
|
||||||
altY
|
altY
|
||||||
shiftTab
|
shiftTab
|
||||||
|
@ -112,6 +113,10 @@ func (s *State) refreshSingleLine(prompt []rune, buf []rune, pos int) error {
|
||||||
|
|
||||||
pLen := countGlyphs(prompt)
|
pLen := countGlyphs(prompt)
|
||||||
bLen := countGlyphs(buf)
|
bLen := countGlyphs(buf)
|
||||||
|
// on some OS / terminals extra column is needed to place the cursor char
|
||||||
|
if cursorColumn {
|
||||||
|
bLen++
|
||||||
|
}
|
||||||
pos = countGlyphs(buf[:pos])
|
pos = countGlyphs(buf[:pos])
|
||||||
if pLen+bLen < s.columns {
|
if pLen+bLen < s.columns {
|
||||||
_, err = fmt.Print(string(buf))
|
_, err = fmt.Print(string(buf))
|
||||||
|
@ -162,6 +167,14 @@ func (s *State) refreshSingleLine(prompt []rune, buf []rune, pos int) error {
|
||||||
func (s *State) refreshMultiLine(prompt []rune, buf []rune, pos int) error {
|
func (s *State) refreshMultiLine(prompt []rune, buf []rune, pos int) error {
|
||||||
promptColumns := countMultiLineGlyphs(prompt, s.columns, 0)
|
promptColumns := countMultiLineGlyphs(prompt, s.columns, 0)
|
||||||
totalColumns := countMultiLineGlyphs(buf, s.columns, promptColumns)
|
totalColumns := countMultiLineGlyphs(buf, s.columns, promptColumns)
|
||||||
|
// on some OS / terminals extra column is needed to place the cursor char
|
||||||
|
// if cursorColumn {
|
||||||
|
// totalColumns++
|
||||||
|
// }
|
||||||
|
|
||||||
|
// it looks like Multiline mode always assume that a cursor need an extra column,
|
||||||
|
// and always emit a newline if we are at the screen end, so no worarounds needed there
|
||||||
|
|
||||||
totalRows := (totalColumns + s.columns - 1) / s.columns
|
totalRows := (totalColumns + s.columns - 1) / s.columns
|
||||||
maxRows := s.maxRows
|
maxRows := s.maxRows
|
||||||
if totalRows > s.maxRows {
|
if totalRows > s.maxRows {
|
||||||
|
@ -583,7 +596,7 @@ func (s *State) Prompt(prompt string) (string, error) {
|
||||||
|
|
||||||
// PromptWithSuggestion displays prompt and an editable text with cursor at
|
// PromptWithSuggestion displays prompt and an editable text with cursor at
|
||||||
// given position. The cursor will be set to the end of the line if given position
|
// given position. The cursor will be set to the end of the line if given position
|
||||||
// is negative or greater than length of text. Returns a line of user input, not
|
// is negative or greater than length of text (in runes). Returns a line of user input, not
|
||||||
// including a trailing newline character. An io.EOF error is returned if the user
|
// including a trailing newline character. An io.EOF error is returned if the user
|
||||||
// signals end-of-file by pressing Ctrl-D.
|
// signals end-of-file by pressing Ctrl-D.
|
||||||
func (s *State) PromptWithSuggestion(prompt string, text string, pos int) (string, error) {
|
func (s *State) PromptWithSuggestion(prompt string, text string, pos int) (string, error) {
|
||||||
|
@ -618,8 +631,8 @@ func (s *State) PromptWithSuggestion(prompt string, text string, pos int) (strin
|
||||||
|
|
||||||
defer s.stopPrompt()
|
defer s.stopPrompt()
|
||||||
|
|
||||||
if pos < 0 || len(text) < pos {
|
if pos < 0 || len(line) < pos {
|
||||||
pos = len(text)
|
pos = len(line)
|
||||||
}
|
}
|
||||||
if len(line) > 0 {
|
if len(line) > 0 {
|
||||||
err := s.refresh(p, line, pos)
|
err := s.refresh(p, line, pos)
|
||||||
|
@ -969,6 +982,35 @@ mainLoop:
|
||||||
pos = 0
|
pos = 0
|
||||||
case end: // End of line
|
case end: // End of line
|
||||||
pos = len(line)
|
pos = len(line)
|
||||||
|
case altD: // Delete next word
|
||||||
|
if pos == len(line) {
|
||||||
|
fmt.Print(beep)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Remove whitespace to the right
|
||||||
|
var buf []rune // Store the deleted chars in a buffer
|
||||||
|
for {
|
||||||
|
if pos == len(line) || !unicode.IsSpace(line[pos]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buf = append(buf, line[pos])
|
||||||
|
line = append(line[:pos], line[pos+1:]...)
|
||||||
|
}
|
||||||
|
// Remove non-whitespace to the right
|
||||||
|
for {
|
||||||
|
if pos == len(line) || unicode.IsSpace(line[pos]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buf = append(buf, line[pos])
|
||||||
|
line = append(line[:pos], line[pos+1:]...)
|
||||||
|
}
|
||||||
|
// Save the result on the killRing
|
||||||
|
if killAction > 0 {
|
||||||
|
s.addToKillRing(buf, 2) // Add in prepend mode
|
||||||
|
} else {
|
||||||
|
s.addToKillRing(buf, 0) // Add in normal mode
|
||||||
|
}
|
||||||
|
killAction = 2 // Mark that there was some killing
|
||||||
case winch: // Window change
|
case winch: // Window change
|
||||||
if s.multiLineMode {
|
if s.multiLineMode {
|
||||||
if s.maxRows-s.cursorRows > 0 {
|
if s.maxRows-s.cursorRows > 0 {
|
||||||
|
|
|
@ -56,9 +56,6 @@ func (s *State) getColumns() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
s.columns = int(ws.col)
|
s.columns = int(ws.col)
|
||||||
if cursorColumn && s.columns > 1 {
|
|
||||||
s.columns--
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,8 +69,4 @@ func (s *State) getColumns() {
|
||||||
var sbi consoleScreenBufferInfo
|
var sbi consoleScreenBufferInfo
|
||||||
procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi)))
|
procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi)))
|
||||||
s.columns = int(sbi.dwSize.x)
|
s.columns = int(sbi.dwSize.x)
|
||||||
if s.columns > 1 {
|
|
||||||
// Windows 10 needs a spare column for the cursor
|
|
||||||
s.columns--
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package liner
|
package liner
|
||||||
|
|
||||||
import "unicode"
|
import (
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/mattn/go-runewidth"
|
||||||
|
)
|
||||||
|
|
||||||
// These character classes are mostly zero width (when combined).
|
// These character classes are mostly zero width (when combined).
|
||||||
// A few might not be, depending on the user's font. Fixing this
|
// A few might not be, depending on the user's font. Fixing this
|
||||||
|
@ -13,13 +17,6 @@ var zeroWidth = []*unicode.RangeTable{
|
||||||
unicode.Cf,
|
unicode.Cf,
|
||||||
}
|
}
|
||||||
|
|
||||||
var doubleWidth = []*unicode.RangeTable{
|
|
||||||
unicode.Han,
|
|
||||||
unicode.Hangul,
|
|
||||||
unicode.Hiragana,
|
|
||||||
unicode.Katakana,
|
|
||||||
}
|
|
||||||
|
|
||||||
// countGlyphs considers zero-width characters to be zero glyphs wide,
|
// countGlyphs considers zero-width characters to be zero glyphs wide,
|
||||||
// and members of Chinese, Japanese, and Korean scripts to be 2 glyphs wide.
|
// and members of Chinese, Japanese, and Korean scripts to be 2 glyphs wide.
|
||||||
func countGlyphs(s []rune) int {
|
func countGlyphs(s []rune) int {
|
||||||
|
@ -31,13 +28,7 @@ func countGlyphs(s []rune) int {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
n += runewidth.RuneWidth(r)
|
||||||
case unicode.IsOneOf(zeroWidth, r):
|
|
||||||
case unicode.IsOneOf(doubleWidth, r):
|
|
||||||
n += 2
|
|
||||||
default:
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
@ -49,17 +40,17 @@ func countMultiLineGlyphs(s []rune, columns int, start int) int {
|
||||||
n++
|
n++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch {
|
switch runewidth.RuneWidth(r) {
|
||||||
case unicode.IsOneOf(zeroWidth, r):
|
case 0:
|
||||||
case unicode.IsOneOf(doubleWidth, r):
|
case 1:
|
||||||
|
n++
|
||||||
|
case 2:
|
||||||
n += 2
|
n += 2
|
||||||
// no room for a 2-glyphs-wide char in the ending
|
// no room for a 2-glyphs-wide char in the ending
|
||||||
// so skip a column and display it at the beginning
|
// so skip a column and display it at the beginning
|
||||||
if n%columns == 1 {
|
if n%columns == 1 {
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
n++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
|
|
|
@ -359,10 +359,10 @@
|
||||||
"revisionTime": "2017-01-12T15:04:04Z"
|
"revisionTime": "2017-01-12T15:04:04Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "lSRg5clrIZUxq4aaExbpnpAgtWA=",
|
"checksumSHA1": "fGwQlTsml12gzgbWjOyyKPzbMD8=",
|
||||||
"path": "github.com/peterh/liner",
|
"path": "github.com/peterh/liner",
|
||||||
"revision": "a37ad39843113264dae84a5d89fcee28f50b35c6",
|
"revision": "a2c9a5303de792c7581a3b80a8f10258319bb20e",
|
||||||
"revisionTime": "2017-09-02T20:46:57Z"
|
"revisionTime": "2019-01-23T17:38:45Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "xCv4GBFyw07vZkVtKF/XrUnkHRk=",
|
"checksumSHA1": "xCv4GBFyw07vZkVtKF/XrUnkHRk=",
|
||||||
|
|
Loading…
Reference in New Issue