initial commit

This commit is contained in:
milarin 2023-03-19 13:08:11 +01:00
commit 72f5fa21c7
9 changed files with 460 additions and 0 deletions

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# slash
Linux shell

62
ansi_seq.go Normal file
View File

@ -0,0 +1,62 @@
package main
import (
"os"
"os/exec"
)
func doItRaw() {
rawMode := exec.Command("/bin/stty", "raw", "-echo")
rawMode.Stdin = os.Stdin
err := rawMode.Run()
if err != nil {
panic(err)
}
}
func cook() {
cookedMode := exec.Command("/bin/stty", "-raw", "echo")
cookedMode.Stdin = os.Stdin
err := cookedMode.Run()
if err != nil {
panic(err)
}
}
func clearEOL() {
os.Stdout.Write([]byte{0x1b, 0x5b, 0x4b})
}
func clear() {
os.Stdout.Write([]byte{0x1b, 0x5b, 0x48, 0x1b, 0x5b, 0x32, 0x4a, 0x1b, 0x5b, 0x33, 0x4a})
}
func goUp() {
os.Stdout.Write([]byte{0x1b, 0x5b, 0x41})
}
func goLeft() {
os.Stdout.Write([]byte{0x08})
}
// func save() {
// cmd := exec.Command("tput", "sc")
// cmd.Stdin = os.Stdin
// cmd.Stdout = os.Stdout
// cmd.Stderr = os.Stderr
// err := cmd.Run()
// if err != nil {
// panic(err)
// }
// }
// func restore() {
// cmd := exec.Command("tput", "rc")
// cmd.Stdin = os.Stdin
// cmd.Stdout = os.Stdout
// cmd.Stderr = os.Stderr
// err := cmd.Run()
// if err != nil {
// panic(err)
// }
// }

46
colorstring.go Normal file
View File

@ -0,0 +1,46 @@
package main
import (
"unicode/utf8"
"github.com/fatih/color"
)
type ColorString struct {
color string
raw string
}
func (s *ColorString) Append(str string, attrs ...color.Attribute) *ColorString {
s.color += color.New(attrs...).Sprint(str)
s.raw += str
return s
}
func (s *ColorString) AppendColorString(str ...*ColorString) *ColorString {
for _, o := range str {
s.color += o.color
s.raw += o.raw
}
return s
}
func (s *ColorString) Equals(other *ColorString) bool {
return s.color == other.color && s.raw == other.raw
}
func (s *ColorString) Len() int {
return utf8.RuneCountInString(s.raw)
}
func (s *ColorString) ColorLen() int {
return utf8.RuneCountInString(s.color)
}
func (s *ColorString) String() string {
return s.color
}
func (s *ColorString) Raw() string {
return s.raw
}

16
go.mod Normal file
View File

@ -0,0 +1,16 @@
module git.milar.in/slash/shell
go 1.17
require (
git.milar.in/milarin/adverr v1.1.0
github.com/fatih/color v1.12.0
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
)
require (
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
)

20
go.sum Normal file
View File

@ -0,0 +1,20 @@
git.milar.in/milarin/adverr v1.1.0 h1:jD9WnOvs40lfMhvqQ7cllOaRJNBMWr1f07/s9jAadp0=
git.milar.in/milarin/adverr v1.1.0/go.mod h1:joU9sBb7ySyNv4SpTXB0Z4o1mjXsArBw4N27wjgzj9E=
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

46
handle.go Normal file
View File

@ -0,0 +1,46 @@
package main
import (
"fmt"
"os"
"os/exec"
"strings"
"git.milar.in/milarin/adverr"
)
func handle(input string) {
input = strings.TrimSpace(input)
if input == "" {
return
}
if input == "up" {
goUp()
return
}
if strings.HasPrefix(input, "cd ") {
os.Chdir(strings.Split(input, " ")[1])
return
}
cook()
defer doItRaw()
parts := strings.Split(input, " ")
cmd := exec.Command(parts[0], parts[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
fmt.Println(adverr.Wrap("Could not start process", err))
return
}
if err := cmd.Wait(); err != nil {
fmt.Println(adverr.Wrap("Could not wait for process to complete", err))
return
}
}

19
input.go Normal file
View File

@ -0,0 +1,19 @@
package main
import (
"os"
)
func handleInput() <-chan byte {
ch := make(chan byte)
go func(ch chan byte) {
data := make([]byte, 1)
for _, err := os.Stdin.Read(data); err == nil; _, err = os.Stdin.Read(data) {
ch <- data[0]
}
close(ch)
}(ch)
return ch
}

161
main.go Normal file
View File

@ -0,0 +1,161 @@
package main
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"unicode"
"github.com/fatih/color"
"golang.org/x/crypto/ssh/terminal"
)
var (
inputBuffer = new(ColorString)
oldLineContent = new(ColorString)
oldWidth int
debugInfo string
)
func main() {
// enable raw mode
doItRaw()
defer cook()
inputChannel := handleInput()
running := true
refreshInputBuffer(true)
for running {
select {
case input := <-inputChannel:
if unicode.IsControl(rune(input)) {
if input == 127 && inputBuffer.Len() > 0 { // Backspace
str := inputBuffer.Raw()
inputBuffer = new(ColorString)
inputBuffer.Append(str[:len(str)-1])
} else if input == 12 { // C-L
clear()
refreshInputBuffer(true)
} else if input == 13 { // Enter
cmd := inputBuffer.Raw()
if strings.TrimSpace(cmd) == "exit" {
running = false
break
}
inputBuffer = new(ColorString)
fmt.Print("\r\n")
handle(cmd)
refreshInputBuffer(true)
} else if input == 3 { // C-C
inputBuffer = new(ColorString)
} else if input == 17 { // C-Q
running = false
break
}
//fmt.Print(input, "\r\n")
break
}
inputBuffer.Append(string(input))
case <-time.After(time.Second):
}
refreshInputBuffer(false)
}
}
func refreshInputBuffer(force bool) {
ps := prompt()
lineContent := new(ColorString).AppendColorString(ps, inputBuffer)
width, _, err := terminal.GetSize(int(os.Stdin.Fd()))
if err != nil {
panic(err)
}
if oldWidth == 0 {
oldWidth = width
}
lines := oldLineContent.Len() / width
debugInfo = strconv.Itoa(lines)
for i := 0; i <= lines; i++ {
fmt.Print("\r")
clearEOL()
if i < lines {
goUp()
}
}
fmt.Print(lineContent.String())
oldLineContent = lineContent
oldWidth = width
}
// func refreshInputBuffer(force bool) {
// width, _, err := terminal.GetSize(int(os.Stdin.Fd()))
// if err != nil {
// panic(err)
// }
// clearEOL()
// debugInfo = fmt.Sprint(oldLineContent.Len(), width, oldLineContent.Len()/width)
// for i := 0; i < oldLineContent.Len()/width; i++ {
// goUp()
// }
// ps := prompt()
// lineContent := new(ColorString).AppendColorString(ps, inputBuffer)
// if !force && lineContent.Equals(oldLineContent) {
// return
// }
// diff := lineContent.Len() - oldLineContent.Len()
// if diff < 0 {
// diff = -diff
// }
// for i := 0; i < diff; i++ {
// goLeft()
// }
// clearEOL()
// fmt.Print("\r")
// fmt.Print(lineContent.String())
// oldLineContent = lineContent
// }
// func getTerminalSize() (width, height int, err error) {
// width, height, err = terminal.GetSize(int(os.Stdout.Fd()))
// if err != nil {
// return 0, 0, adverr.Wrap("Could not retrieve terminal size", err)
// }
// return
// }
func print(str string, attrs ...color.Attribute) string {
return color.New(attrs...).Sprint(str)
}
func host() string {
hostname, _ := os.Hostname()
return hostname
}
func wd() string {
wd, _ := os.Getwd()
home := env("HOME")
return strings.Replace(wd, home, "~", 1)
}
func env(key string) string {
value, _ := os.LookupEnv(key)
return value
}

87
prompt.go Normal file
View File

@ -0,0 +1,87 @@
package main
import (
"time"
"github.com/fatih/color"
)
var prompts = []func() *ColorString{ps1, ps2, ps3, ps4, ps5}
func prompt() *ColorString {
// width, _, err := terminal.GetSize(int(os.Stdin.Fd()))
// if err != nil {
// return ">"
// }
return ps1()
// for _, pfunc := range prompts {
// prompt := pfunc()
// if prompt.Len()+inputBuffer.Len() < width {
// return prompt.String()
// }
// }
}
func ps1() *ColorString {
p := new(ColorString)
p.Append(time.Now().Format("15:04:05"), color.FgRed)
p.Append(" • ")
p.Append(debugInfo, color.FgMagenta)
if len(debugInfo) > 0 {
p.Append(" • ")
}
p.Append(env("USER"), color.FgGreen)
p.Append(" • ")
p.Append(host(), color.FgYellow)
p.Append(" • ")
p.Append(wd(), color.FgBlue)
p.Append(" ", color.FgRed)
p.Append("", color.FgYellow)
p.Append(" ", color.FgGreen)
return p
}
func ps2() *ColorString {
p := new(ColorString)
p.Append(env("USER"), color.FgGreen)
p.Append(" • ")
p.Append(host(), color.FgYellow)
p.Append(" • ")
p.Append(wd(), color.FgBlue)
p.Append(" ", color.FgRed)
p.Append("", color.FgYellow)
p.Append(" ", color.FgGreen)
return p
}
func ps3() *ColorString {
p := new(ColorString)
p.Append(env("USER"), color.FgGreen)
p.Append(" • ")
p.Append(wd(), color.FgBlue)
p.Append(" ", color.FgRed)
p.Append("", color.FgYellow)
p.Append(" ", color.FgGreen)
return p
}
func ps4() *ColorString {
p := new(ColorString)
p.Append(wd(), color.FgBlue)
p.Append(" ", color.FgRed)
p.Append("", color.FgYellow)
p.Append(" ", color.FgGreen)
return p
}
func ps5() *ColorString {
p := new(ColorString)
p.Append(" ", color.FgRed)
p.Append("", color.FgYellow)
p.Append(" ", color.FgGreen)
return p
}