Hilbish/readline/register.go

333 lines
9.4 KiB
Go

package readline
import (
"fmt"
"sort"
"strconv"
"strings"
"sync"
"github.com/evilsocket/islazy/tui"
)
// registers - Contains all memory registers resulting from delete/paste/search
// or other operations in the command line input.
type registers struct {
unnamed []rune // Unnamed register, used by default
num map[int][]rune // numbered registers (0-9)
alpha map[string][]rune // lettered registers ( a-z )
ro map[string][]rune // read-only registers ( . % : )
registerSelectWait bool // The user wants to use a still unidentified register
onRegister bool // We have identified the register, and acting on it.
currentRegister rune // Any of the read/write registers ("/num/alpha)
mutex *sync.Mutex
}
func (rl *Instance) initRegisters() {
rl.registers = &registers{
num: make(map[int][]rune, 10),
alpha: make(map[string][]rune, 52),
ro: map[string][]rune{},
mutex: &sync.Mutex{},
}
}
// saveToRegister - Passing a function that will move around the line in the desired way, we get
// the number of Vim iterations and we save the resulting string to the appropriate buffer.
// It's the same as saveToRegisterTokenize, but without the need to generate tokenized &
// cursor-pos-actualized versions of the input line.
func (rl *Instance) saveToRegister(adjust int) {
// Get the current cursor position and go the length specified.
var begin = rl.pos
var end = rl.pos
end += adjust
if end > len(rl.line)-1 {
end = len(rl.line)
} else if end < 0 {
end = 0
}
var buffer []rune
if end < begin {
buffer = rl.line[end:begin]
} else {
buffer = rl.line[begin:end]
}
// Make an immutable copy of the buffer before saving it
buf := string(buffer)
// Put the buffer in the appropriate registers.
// By default, always in the unnamed one first.
rl.saveBufToRegister([]rune(buf))
}
// saveToRegisterTokenize - Passing a function that will move around the line in the desired way, we get
// the number of Vim iterations and we save the resulting string to the appropriate buffer. Because we
// need the cursor position to be really moved around between calls to the jumper, we also need the tokeniser.
func (rl *Instance) saveToRegisterTokenize(tokeniser tokeniser, jumper func(tokeniser) int, vii int) {
// The register is going to have to heavily manipulate the cursor position.
// Remember the original one first, for the end.
var beginPos = rl.pos
// Get the current cursor position and go the length specified.
var begin = rl.pos
for i := 1; i <= vii; i++ {
rl.moveCursorByAdjust(jumper(tokeniser))
}
var end = rl.pos
rl.pos = beginPos
if end > len(rl.line)-1 {
end = len(rl.line)
} else if end < 0 {
end = 0
}
var buffer []rune
if end < begin {
buffer = rl.line[end:begin]
} else {
buffer = rl.line[begin:end]
}
// Make an immutable copy of the buffer before saving it
buf := string(buffer)
// Put the buffer in the appropriate registers.
// By default, always in the unnamed one first.
rl.saveBufToRegister([]rune(buf))
}
// saveBufToRegister - Instead of computing the buffer ourselves based on an adjust,
// let the caller pass directly this buffer, yet relying on the register system to
// determine which register will store the buffer.
func (rl *Instance) saveBufToRegister(buffer []rune) {
rl.SetRegisterBuf(string(rl.registers.currentRegister), buffer)
}
func (rl *Instance) SetRegisterBuf(reg string, buffer []rune) {
// We must make an immutable version of the buffer first.
buf := string(buffer)
// When exiting this function the currently selected register is dropped,
defer rl.registers.resetRegister()
// If the buffer is empty, just return
if len(buffer) == 0 || buf == "" {
return
}
// Put the buffer in the appropriate registers.
// By default, always in the unnamed one first.
rl.registers.unnamed = []rune(buf)
// If there is an active register, directly give it the buffer.
// Check if its a numbered or lettered register, and put it in.
if rl.registers.onRegister {
num, err := strconv.Atoi(reg)
if err == nil && num < 10 {
rl.registers.writeNumberedRegister(num, []rune(buf), false)
} else if err != nil {
rl.registers.writeAlphaRegister([]rune(buf))
}
} else {
// Or, if no active register and if there is room on the numbered ones,
rl.registers.writeNumberedRegister(0, []rune(buf), true)
}
}
// The user asked to paste a buffer onto the line, so we check from which register
// we are supposed to select the buffer, and return it to the caller for insertion.
func (rl *Instance) pasteFromRegister() (buffer []rune) {
// When exiting this function the currently selected register is dropped,
defer rl.registers.resetRegister()
// If no actively selected register, return the unnamed buffer
if !rl.registers.registerSelectWait && !rl.registers.onRegister {
return rl.registers.unnamed
}
activeRegister := string(rl.registers.currentRegister)
return rl.GetFromRegister(activeRegister)
}
func (rl *Instance) GetFromRegister(reg string) []rune {
// Find the active register, and return its content.
num, err := strconv.Atoi(reg)
// Either from the numbered ones.
if err == nil {
buf, found := rl.registers.num[num]
if found {
return buf
}
return []rune{}
}
// or the lettered ones
buf, found := rl.registers.alpha[reg]
if found {
return buf
}
// Or the read-only ones
buf, found = rl.registers.ro[reg]
if found {
return buf
}
return []rune{}
}
// setActiveRegister - The user has typed "<regiserID>, and we don't know yet
// if we are about to copy to/from it, so we just set as active, so that when
// the action to perform on it will be asked, we know which one to use.
func (r *registers) setActiveRegister(reg rune) {
defer func() {
// We now have an active, identified register
r.registerSelectWait = false
r.onRegister = true
}()
// Numbered
num, err := strconv.Atoi(string(reg))
if err == nil && num < 10 {
r.currentRegister = reg
return
}
// Read-only
_, found := r.ro[string(reg)]
if found {
r.currentRegister = reg
return
}
// Else, lettered
r.currentRegister = reg
}
// writeNumberedRegister - Add a buffer to one of the numbered registers
// Pass a number above 10 to indicate we just push it on the num stack.
func (r *registers) writeNumberedRegister(idx int, buf []rune, push bool) {
// No numbered register above 10
if len(r.num) > 10 {
return
}
// No push to the stack if we are already using 9
var max int
if push {
for i := range r.num {
if i > max && string(r.num[i]) != string(buf) {
max = i
}
}
if max < 9 {
r.num[max+1] = buf
}
} else {
// Add to the stack with the specified register
r.num[idx] = buf
}
}
// writeAlphaRegister - Either adds a buffer to a new/existing letterd register,
// or appends to this new/existing register if the currently active register is
// the uppercase letter for this register.
func (r *registers) writeAlphaRegister(buffer []rune) {
appendRegs := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
appended := false
for _, char := range appendRegs {
if char == r.currentRegister {
real := strings.ToLower(string(r.currentRegister))
_, exists := r.alpha[real]
if exists {
r.alpha[real] = append(r.alpha[real], buffer...)
} else {
r.alpha[real] = buffer
}
appended = true
}
}
if !appended {
r.alpha[string(r.currentRegister)] = buffer
}
}
// resetRegister - there is no currently active register anymore,
// nor we are currently setting one as active.
func (r *registers) resetRegister() {
r.currentRegister = ' '
r.registerSelectWait = false
r.onRegister = false
}
// The user can show registers completions and insert, no matter the cursor position.
func (rl *Instance) completeRegisters() (groups []*CompletionGroup) {
// We set the info exceptionally
info := BLUE + "-- registers --" + RESET
rl.infoText = []rune(info)
// Make the groups
anonRegs := &CompletionGroup{
DisplayType: TabDisplayMap,
MaxLength: 20,
Descriptions: map[string]string{},
}
// Unnamed (the added space is because we must have a unique key.
// This space is trimmed when the buffer is being passed to users)
anonRegs.Suggestions = append(anonRegs.Suggestions, string(rl.registers.unnamed))
anonRegs.Descriptions[string(rl.registers.unnamed)] = DIM + "\"" + "\"" + RESET
groups = append(groups, anonRegs)
// Numbered registers
numRegs := &CompletionGroup{
Name: tui.DIM + "num ([0-9])" + tui.RESET,
DisplayType: TabDisplayMap,
MaxLength: 20,
Descriptions: map[string]string{},
}
var nums []int
for reg := range rl.registers.num {
nums = append(nums, reg)
}
sort.Ints(nums)
for _, val := range nums {
buf := rl.registers.num[val]
numRegs.Suggestions = append(numRegs.Suggestions, string(buf))
numRegs.Descriptions[string(buf)] = fmt.Sprintf("%s\"%d%s", DIM, val, RESET)
}
if len(numRegs.Suggestions) > 0 {
groups = append(groups, numRegs)
}
// Letter registers
alphaRegs := &CompletionGroup{
Name: tui.DIM + "alpha ([a-z], [A-Z])" + tui.RESET,
DisplayType: TabDisplayMap,
MaxLength: 20,
Descriptions: map[string]string{},
}
var lett []string
for reg := range rl.registers.alpha {
lett = append(lett, reg)
}
sort.Strings(lett)
for _, reg := range lett {
buf := rl.registers.alpha[reg]
alphaRegs.Suggestions = append(alphaRegs.Suggestions, string(buf))
alphaRegs.Descriptions[string(buf)] = DIM + "\"" + reg + RESET
}
if len(alphaRegs.Suggestions) > 0 {
groups = append(groups, alphaRegs)
}
return
}