Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions bashwordcontroller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package liner

type (
// BashWordController describes bash-like behavior for the word related functions
BashWordController struct {
DefaultWordController
}
)

// NewBashWordController returns default word controller with default settings
func NewBashWordController() BashWordController {
return BashWordController{
DefaultWordController: DefaultWordController{
wordSeparatorChecker: CombineWordSeparatorChecker(
SpaceWordSeparatorChecker,
PunctWordSeparatorChecker,
),
},
}
}

// EraseWordBack returns an effect for ctrlW
func (bc BashWordController) EraseWordBack(line []rune, originalPos int) (effect, error) {
pos := originalPos
if pos == 0 {
return effect{newPosition: pos, beep: true}, nil
}

// Remove word separators to the left
for {
if pos == 0 || !bc.isSpace(line[pos-1]) {
break
}
pos--
}

// Remove non-word separators to the left
for {
if pos == 0 || bc.isSpace(line[pos-1]) {
break
}
pos--
}

return effect{
toDelete: &deleteEffect{
from: pos,
to: originalPos,
},
newPosition: pos,
}, nil
}

func (bc BashWordController) isSpace(r rune) bool {
return SpaceWordSeparatorChecker(r)
}

var _ WordController = (*BashWordController)(nil)
6 changes: 6 additions & 0 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type commonState struct {
maxRows int
shouldRestart ShouldRestart
needRefresh bool
wordController WordController
}

// TabStyle is used to select how tab completions are displayed.
Expand Down Expand Up @@ -253,3 +254,8 @@ func (s *State) promptUnsupported(p string) (string, error) {
}
return string(linebuf), nil
}

// SetWordController sets a behavior for word jump related functions
func (s *State) SetWordController(wc WordController) {
s.wordController = wc
}
13 changes: 13 additions & 0 deletions effect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package liner

type (
deleteEffect struct {
from, to int
}

effect struct {
beep bool
toDelete *deleteEffect
newPosition int // position after modification
}
)
2 changes: 2 additions & 0 deletions input.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ func NewLiner() *State {
s.outputRedirected = !s.getColumns()
}

s.wordController = NewDefaultWordController()

return &s
}

Expand Down
107 changes: 34 additions & 73 deletions line.go
Original file line number Diff line number Diff line change
Expand Up @@ -812,29 +812,20 @@ mainLoop:
pos = 0
s.needRefresh = true
case ctrlW: // Erase word
if pos == 0 {
effect, _ := s.wordController.EraseWordBack(line, pos)
if effect.beep {
fmt.Print(beep)
break
}
// Remove whitespace to the left
var buf []rune // Store the deleted chars in a buffer
for {
if pos == 0 || !unicode.IsSpace(line[pos-1]) {
break
}
buf = append(buf, line[pos-1])
line = append(line[:pos-1], line[pos:]...)
pos--
}
// Remove non-whitespace to the left
for {
if pos == 0 || unicode.IsSpace(line[pos-1]) {
break
}
buf = append(buf, line[pos-1])
line = append(line[:pos-1], line[pos:]...)
pos--
if effect.toDelete == nil {
break
}

toDelete := effect.toDelete
buf := line[toDelete.from:toDelete.to]
line = append(line[:toDelete.from], line[toDelete.to:]...)

pos = effect.newPosition

// Invert the buffer and save the result on the killRing
var newBuf []rune
for i := len(buf) - 1; i >= 0; i-- {
Expand Down Expand Up @@ -896,53 +887,24 @@ mainLoop:
fmt.Print(beep)
}
case wordLeft, altB:
if pos > 0 {
var spaceHere, spaceLeft, leftKnown bool
for {
pos--
if pos == 0 {
break
}
if leftKnown {
spaceHere = spaceLeft
} else {
spaceHere = unicode.IsSpace(line[pos])
}
spaceLeft, leftKnown = unicode.IsSpace(line[pos-1]), true
if !spaceHere && spaceLeft {
break
}
}
} else {
effect, _ := s.wordController.WordLeft(line, pos)
if effect.beep {
fmt.Print(beep)
}

pos = effect.newPosition
case right:
if pos < len(line) {
pos += len(getPrefixGlyphs(line[pos:], 1))
} else {
fmt.Print(beep)
}
case wordRight, altF:
if pos < len(line) {
var spaceHere, spaceLeft, hereKnown bool
for {
pos++
if pos == len(line) {
break
}
if hereKnown {
spaceLeft = spaceHere
} else {
spaceLeft = unicode.IsSpace(line[pos-1])
}
spaceHere, hereKnown = unicode.IsSpace(line[pos]), true
if spaceHere && !spaceLeft {
break
}
}
} else {
effect, _ := s.wordController.WordRight(line, pos)
if effect.beep {
fmt.Print(beep)
}
pos = effect.newPosition
case up:
historyAction = true
if historyStale {
Expand Down Expand Up @@ -983,27 +945,26 @@ mainLoop:
case end: // End of line
pos = len(line)
case altD: // Delete next word
if pos == len(line) {
effect, _ := s.wordController.DeleteNextWord(line, pos)
if effect.beep {
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:]...)
if effect.toDelete == nil {
break
}
// 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:]...)

toDelete := effect.toDelete
buf := line[toDelete.from:toDelete.to]
line = append(line[:toDelete.from], line[toDelete.to:]...)

pos = effect.newPosition

// Invert the buffer and save the result on the killRing
var newBuf []rune
for i := len(buf) - 1; i >= 0; i-- {
newBuf = append(newBuf, buf[i])
}

// Save the result on the killRing
if killAction > 0 {
s.addToKillRing(buf, 2) // Add in prepend mode
Expand Down
Loading