2
3
Fork 0
A simple shell with simple goals https://git.rawtext.club/sloum/slosh
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

226 lines
4.2 KiB

package main
import (
"fmt"
"io/fs"
"io/ioutil"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
ln "github.com/peterh/liner"
)
const (
History_Filename string = "~/.slosh_history"
InModeLiner int = 0
InModeInitial int = 1
)
var (
completion_names = map[string]bool{"cd":true,"exit":true,"up":true,"alias": true,"unalias":true,"set":true,"unset":true,"let":true,"lsf":true}
slosh_vars = map[string]string{}
alias = map[string]CommandLine{}
slosh_prompt string
last_exit_code int = 0
initialTerm ln.ModeApplier = nil
linerTerm ln.ModeApplier = nil
currentMode int = 0
Hostname string
IsLoginShell bool = false
)
func setUpLineInput() *ln.State {
var err error
initialTerm, err = ln.TerminalMode()
if err != nil {
initialTerm = nil
}
l := ln.NewLiner()
linerTerm, err = ln.TerminalMode()
if err != nil {
linerTerm = nil
}
l.SetCtrlCAborts(true)
l.SetTabCompletionStyle(ln.TabPrints)
l.SetCompleter(func(line string) []string {
c := make([]string, 0, 5)
if line == "" {
return c
}
_, l := ParseCommandLine(line)
current := l[len(l)-1]
// Add completion for current filepath
if isFilepath(current) {
p := current
if !strings.HasPrefix(p, "/") && !strings.HasPrefix(p, "~") {
wd, _ := os.Getwd()
p = filepath.Join(wd, current)
}
var rootPath string
var match string
if strings.HasSuffix(p, "/") {
rootPath = ExpandedAbsFilepath(p)
match = ""
} else {
match = filepath.Base(p)
rootPath = ExpandedAbsFilepath(filepath.Dir(p))
}
fInfo, err := ioutil.ReadDir(rootPath)
if err == nil {
for _, f := range fInfo {
if strings.HasPrefix(f.Name(), match) {
out := f.Name()
suffix := ""
if f.IsDir() {
suffix = "/"
}
suggestion := fmt.Sprintf(
"%s %s%s",
strings.TrimSpace(strings.Join(l[:len(l)-1], " ")),
filepath.Join(rootPath, out),
suffix,
)
c = append(c, suggestion)
}
}
}
}
// Add completion for aliases
for key, _ := range alias {
if strings.HasPrefix(key, strings.ToLower(line)) {
c = append(c, key)
}
}
// Add completion for path files
for key, _ := range completion_names {
if strings.HasPrefix(key, strings.ToLower(line)) {
c = append(c, key)
}
}
return c
})
return l
}
func setCompletionNames() {
pathvar := os.Getenv("PATH")
elmts := strings.Split(pathvar, ":")
for i := range elmts {
filepath.Walk(elmts[i], func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
b := filepath.Base(path)
if b == "." {
return nil
}
completion_names[b] = true
return nil
},
)
}
}
func prompt(ln *ln.State) string {
val, err := ln.Prompt(getSloshPrompt())
if err != nil {
return ""
}
ln.AppendHistory(val)
return val
}
func getSloshPrompt() string {
p := os.Getenv("SLOSH_PROMPT")
wd, _ := os.Getwd()
if p == "" {
return wd + " # "
}
var prev rune = 0
var out strings.Builder
for _, c := range []rune(p) {
if c == '%' {
prev = c
} else if prev == '%' {
out.WriteString(promptReplace(c, wd))
prev = c
} else {
out.WriteRune(c)
prev = c
}
}
return out.String()
}
func initShell() *ln.State {
Hostname, _ = os.Hostname()
setCompletionNames()
LoadSloshFiles() // Loads login and rc
return setUpLineInput()
}
func handleSignals(c <-chan os.Signal) {
for {
switch <-c {
case syscall.SIGINT:
if !IsLoginShell {
initialTerm.ApplyMode()
os.Exit(1)
}
case syscall.SIGTSTP, syscall.SIGCONT:
if CurrentProcess != nil {
CurrentProcess.Process.Signal(syscall.SIGINT)
}
}
}
}
func main() {
if os.Args[0][0] == '-' {
IsLoginShell = true
}
ln := initShell()
defer ln.Close()
if f, e := os.Open(ExpandedAbsFilepath(History_Filename)); e == nil {
ln.ReadHistory(f)
f.Close()
}
c := make(chan os.Signal)
signal.Ignore(syscall.SIGTTIN, syscall.SIGTTOU)
signal.Notify(c, syscall.SIGINT, syscall.SIGQUIT)
go handleSignals(c)
for {
in := prompt(ln)
if len(in) == 0 {
continue
} else if in == "exit" {
break
}
entry, _ := ParseCommandLine(in)
entry.Execute()
}
if f, e := os.Create(ExpandedAbsFilepath(History_Filename)); e == nil {
ln.WriteHistory(f)
f.Close()
}
}