diff --git a/base/web/space.go b/base/web/space.go index 9362f143..fe6bb9bd 100644 --- a/base/web/space.go +++ b/base/web/space.go @@ -192,8 +192,6 @@ func init() { m.PushSearch(mdb.TEXT, m.MergePod(value[mdb.NAME]), value) } }) - } else if arg[0] == mdb.FOREACH && arg[1] == ssh.SHELL { - m.PushSearch(mdb.TYPE, ssh.SHELL, mdb.TEXT, "ice.bin space dial dev ops") } }}, DOMAIN: {Hand: func(m *ice.Message, arg ...string) { m.Echo(_space_domain(m)) }}, diff --git a/core/code/go.go b/core/code/go.go index 68930910..33fe9ba0 100644 --- a/core/code/go.go +++ b/core/code/go.go @@ -101,28 +101,22 @@ func init() { Index.MergeCommands(ice.Commands{ GO: {Name: "go path auto", Help: "后端编程", Actions: ice.MergeActions(ice.Actions{ mdb.RENDER: {Hand: func(m *ice.Message, arg ...string) { - if arg[1] == "main.go" { - // ProcessXterm(m, ssh.WEBIO, "", arg[1]) - ProcessXterm(m, "ice.bin source stdio", "", arg[1]) - return - } - ctx.ProcessCommand(m, yac.STACK, kit.Simple(path.Join(arg[2], arg[1]))) - return - cmds, text := "ice.bin source stdio", ctx.GetFileCmd(path.Join(arg[2], arg[1])) - if text != "" { - ls := strings.Split(text, nfs.PT) - text = "~" + kit.Join(kit.Slice(ls, 0, -1), nfs.PT) + lex.NL + kit.Slice(ls, -1)[0] + if arg[1] == "misc/xterm/iterm.go" { + ProcessXterm(m, "ish", "", arg[1]) + } else if arg[1] == "main.go" { + ProcessXterm(m, "ish", "", arg[1]) } else { - text = "cli.system go run " + path.Join(arg[2], arg[1]) + ctx.ProcessCommand(m, yac.STACK, kit.Simple(path.Join(arg[2], arg[1]))) } - ProcessXterm(m, cmds, text, arg[1]) }}, mdb.ENGINE: {Hand: func(m *ice.Message, arg ...string) { - if cmd := ctx.GetFileCmd(path.Join(arg[2], arg[1])); cmd != "" { + if arg[1] == "misc/xterm/iterm.go" { + ProcessXterm(m, "ish", "", arg[1]) + } else if arg[1] == "main.go" { + ProcessXterm(m, "ish", "", arg[1]) + } else if cmd := ctx.GetFileCmd(path.Join(arg[2], arg[1])); cmd != "" { ctx.ProcessCommand(m, cmd, kit.Simple()) - return - } - if msg := m.Cmd(yac.STACK, path.Join(arg[2], arg[1])); msg.Option("__index") != "" { + } else if msg := m.Cmd(yac.STACK, path.Join(arg[2], arg[1])); msg.Option("__index") != "" { ctx.ProcessCommand(m, msg.Option("__index"), kit.Simple()) } else { ctx.ProcessCommand(m, yac.STACK, kit.Simple(path.Join(arg[2], arg[1]))) diff --git a/core/code/xterm.go b/core/code/xterm.go index dd0357a6..b1378578 100644 --- a/core/code/xterm.go +++ b/core/code/xterm.go @@ -17,7 +17,7 @@ import ( kit "shylinux.com/x/toolkits" ) -func _xterm_get(m *ice.Message, h string) *xterm.XTerm { +func _xterm_get(m *ice.Message, h string) xterm.XTerm { h = kit.Select(m.Option(mdb.HASH), h) m.Assert(h != "") mdb.HashModify(m, mdb.TIME, m.Time(), cli.DAEMON, m.Option(ice.MSG_DAEMON)) @@ -32,7 +32,7 @@ func _xterm_get(m *ice.Message, h string) *xterm.XTerm { m.Go(func() { defer term.Close() defer mdb.HashRemove(m, mdb.HASH, h) - m.Log(cli.START, strings.Join(term.Args, lex.SP)) + // m.Log(cli.START, strings.Join(term.Args, lex.SP)) buf := make([]byte, ice.MOD_BUFS) for { if n, e := term.Read(buf); !m.Warn(e) && e == nil { @@ -52,11 +52,12 @@ func _xterm_get(m *ice.Message, h string) *xterm.XTerm { } }) return term - }).(*xterm.XTerm) + }).(xterm.XTerm) } func _xterm_echo(m *ice.Message, h string, str string) { m.Options(ice.MSG_DAEMON, mdb.HashSelectField(m, h, cli.DAEMON)) - m.Option(ice.LOG_DISABLE, ice.TRUE) + // m.Option(ice.LOG_DISABLE, ice.TRUE) + m.Debug("what ---%o--- ---[%v]---", []byte(str), str) web.PushNoticeGrow(m, h, str) } func _xterm_cmds(m *ice.Message, h string, cmd string, arg ...ice.Any) { @@ -77,14 +78,15 @@ func init() { return []string{ssh.SHELL, SH, "/bin/sh"} } }) + mdb.IsSearchForEach(m, arg, func() []string { return []string{ssh.SHELL, "ice", "ish"} }) }}, mdb.INPUTS: {Hand: func(m *ice.Message, arg ...string) { switch mdb.HashInputs(m, arg); arg[0] { case mdb.TYPE: + m.Push(arg[0], "ish", BASH, SH) m.Cmd(mdb.SEARCH, mdb.FOREACH, ssh.SHELL, ice.OptionFields("type,name,text"), func(value ice.Maps) { kit.If(value[mdb.TYPE] == ssh.SHELL, func() { m.Push(arg[0], value[mdb.TEXT]) }) }) - m.Push(arg[0], BASH, SH) case mdb.NAME: m.Push(arg[0], path.Base(m.Option(mdb.TYPE)), ice.Info.Hostname) case nfs.PATH: @@ -102,11 +104,13 @@ func init() { m.Cmd(ctx.COMMAND, mdb.SEARCH, ctx.COMMAND, "", "", ice.OptionFields(ctx.INDEX), func(value ice.Maps) { push(ctx.INDEX, value[ctx.INDEX]) }) } }}, + mdb.CREATE: {Hand: func(m *ice.Message, arg ...string) { m.ProcessRewrite(mdb.HASH, mdb.HashCreate(m, arg)) }}, web.RESIZE: {Hand: func(m *ice.Message, arg ...string) { _xterm_get(m, "").Setsize(m.OptionDefault("rows", "24"), m.OptionDefault("cols", "80")) }}, web.INPUT: {Hand: func(m *ice.Message, arg ...string) { if b, e := base64.StdEncoding.DecodeString(strings.Join(arg, "")); !m.Warn(e) { + m.Debug("what ---%o--- ---[%v]---", b, string(b)) _xterm_get(m, "").Write(string(b)) } }}, diff --git a/exec.go b/exec.go index ee0e55b6..45cf97ed 100644 --- a/exec.go +++ b/exec.go @@ -102,6 +102,13 @@ func (m *Message) CmdHand(cmd *Command, key string, arg ...string) *Message { } return m } +func (m *Message) CmdList(arg ...string) []string { + msg, list := m.Cmd(arg), []string{} + kit.For(msg._cmd.List, func(value Map) { + kit.If(!kit.IsIn(kit.Format(kit.Value(value, TYPE)), "button"), func() { list = append(list, kit.Format(kit.Value(value, NAME))) }) + }) + return msg.Appendv(kit.Select(kit.Select("", list, 0), list, len(arg)-1)) +} func (m *Message) ActionHand(cmd *Command, key, sub string, arg ...string) *Message { if action, ok := cmd.Actions[sub]; !m.Warn(!ok, ErrNotFound, sub, cmd.FileLines()) { return m.Target()._action(m, cmd, key, sub, action, arg...) diff --git a/misc/xterm/iterm.go b/misc/xterm/iterm.go new file mode 100644 index 00000000..fbcf041b --- /dev/null +++ b/misc/xterm/iterm.go @@ -0,0 +1,311 @@ +package xterm + +import ( + "os" + "strings" + "time" + + ice "shylinux.com/x/icebergs" + "shylinux.com/x/icebergs/base/cli" + "shylinux.com/x/icebergs/base/ctx" + "shylinux.com/x/icebergs/base/lex" + "shylinux.com/x/icebergs/base/mdb" + "shylinux.com/x/icebergs/base/nfs" + kit "shylinux.com/x/toolkits" +) + +type idata struct { + cut string + bak string + arg string + app string + tip string + due string + args []string + cmds []string + list []string + pos int +} +type iterm struct { + m *ice.Message + r *os.File + w *os.File + *idata +} + +func newiterm(m *ice.Message) (XTerm, error) { + r, w, e := os.Pipe() + return &iterm{m: m, r: r, w: w, idata: &idata{cmds: append(append(kit.SortedKey(ice.Info.Index), m.Cmd(ctx.COMMAND).Appendv(ctx.INDEX)...), m.Cmd(nfs.DIR, "/bin", mdb.NAME).Appendv(mdb.NAME)...)}}, e +} +func (s iterm) Setsize(rows, cols string) error { + s.w.Write([]byte(s.prompt())) + return nil +} +func (s iterm) Writeln(data string, arg ...ice.Any) { s.Write(kit.Format(data, arg...) + lex.NL) } +func (s iterm) Write(data string) (int, error) { + res, ctrl := "", "" + for _, c := range data { + switch c := string(c); c { + case SOH: // Ctrl+A + res += s.repeat(s.arg) + s.arg, s.app = "", s.arg+s.app + case STX: // Ctrl+B + res = s.left(res) + case ETX: // Ctrl+C + s.m.Cmd("web.code.vimer", "compile") + case EOT: // Ctrl+D + if len(s.app) > 0 { + s.app = s.app[1:] + res = s.rest(res) + } + case ENQ: // Ctrl+E + res += s.repeat(s.app, ESC_C) + s.arg, s.app = s.arg+s.app, "" + case ACK: // Ctrl+F + res = s.right(res) + case BEL: // Ctrl+G + res += c + case BS: // Ctrl+H + res = s.dels(res) + case NL: // Ctrl+J + res = s.exec(res) + case VT: // Ctrl+K + if len(s.app) > 0 { + s.cut, s.app = s.app, "" + res = s.rest(res) + } + case NP: // Ctrl+L + res = s.rest(res + ESC_H + ESC_2J + s.prompt() + s.arg) + case CR: // Ctrl+M + res = s.exec(res) + case SO: // Ctrl+N + res = s.hist(res, 1) + case SI: // Ctrl+O + if s.arg == "" { + s.arg = kit.Select("", s.list, -1) + res += s.arg + res = s.exec(res) + } else { + res = s.exec(res) + } + case DLE: // Ctrl+P + res = s.hist(res, -1) + case DC1: // Ctrl+Q + case DC2: // Ctrl+R + case DC3: // Ctrl+S + case DC4: // Ctrl+T + if n := len(s.arg); n > 1 { + s.arg = s.arg[:n-2] + string(s.arg[n-1]) + string(s.arg[n-2]) + res = s.rest(res + BS + BS + s.arg[n-2:]) + } + case NAK: // Ctrl+U + if len(s.arg) > 0 { + res = res + s.repeat(s.arg) + s.cut, s.arg = s.arg, "" + res = s.rest(res) + } + case SYN: // Ctrl+V + case ETB: // Ctrl+W + arg := s.arg + for len(s.arg) > 0 { + if c := s.arg[len(s.arg)-1]; c == ' ' { + s.arg = s.arg[:len(s.arg)-1] + res += s.repeat(string(c)) + continue + } + break + } + for len(s.arg) > 0 { + if c := s.arg[len(s.arg)-1]; len(s.arg) == 1 || c != ' ' { + s.arg = s.arg[:len(s.arg)-1] + res += s.repeat(string(c)) + continue + } + break + } + s.cut = strings.TrimPrefix(arg, s.arg) + res = s.rest(res) + case CAN: // Ctrl+X + case EM: // Ctrl+Y + s.arg += s.cut + res = s.rest(res + s.cut) + case SUB: // Ctrl+Z + case DEL: + res = s.dels(res) + case ESC: // Ctrl+[ + ctrl = c + default: + if ctrl == "" { + if c == HT { // Ctrl+I + if s.tip != "" { + s.arg += s.app + s.tip + res += s.app + s.tip + s.app = "" + break + } + } + s.arg += c + res = s.rest(res + c) + break + } else if ctrl == ESC && c == "[" { + ctrl += c + break + } else if ctrl == ESC+"[" { + switch c { + case "A": // ArrowUp + res = s.hist(res, -1) + case "B": // ArrowDown + res = s.hist(res, 1) + case "C": // ArrowRight + res = s.right(res) + case "D": // ArrowLeft + res = s.left(res) + } + } + ctrl = "" + } + } + s.w.Write([]byte(res)) + return len(data), nil +} +func (s iterm) Read(buf []byte) (int, error) { + return s.r.Read(buf) +} +func (s iterm) Close() error { return nil } +func (s iterm) style(style string, str string) string { + return kit.Format("\033[%sm%s\033[0m", style, str) +} +func (s iterm) prompt() string { + return kit.Format("%s%d[%s]$ ", CR+ESC_K, len(s.list), time.Now().Format("15:04:05")) +} +func (s iterm) repeat(str string, arg ...string) string { + count := 0 + for _, c := range str { + switch c { + case '\t': + count += 4 + default: + count++ + } + } + return strings.Repeat(kit.Select(BS, arg, 0), count) +} +func (s iterm) left(res string) string { + if n := len(s.arg); n > 0 { + s.arg, s.app = s.arg[:n-1], s.arg[n-1:]+s.app + res += BS + } + return res +} +func (s iterm) right(res string) string { + if len(s.app) > 0 { + s.arg, s.app = s.arg+s.app[:1], s.app[1:] + res += ESC_C + } + return res +} +func (s iterm) dels(res string) string { + if len(s.arg) > 0 { + s.arg = s.arg[:len(s.arg)-1] + res = s.rest(res + BS) + } + return res +} +func (s iterm) tips(arg string) (tip string) { + if kit.HasSuffix(s.arg+s.app, lex.SP, nfs.PS) { + s.args = s.m.CmdList(kit.Split(s.arg + s.app)...) + s.due = CRNL + ESC_K + strings.Join(s.args, lex.SP) + } else if len(s.args) > 0 { + args, key := []string{}, kit.Select("", kit.Split(s.arg+s.app), -1) + kit.For(s.args, func(arg string) { kit.If(strings.HasPrefix(arg, key), func() { args = append(args, arg) }) }) + s.due = CRNL + ESC_K + strings.Join(args, lex.SP) + return strings.TrimPrefix(kit.Select("", args, 0), key) + } + for i := len(s.list) - 1; i >= 0; i-- { + if v := s.list[i]; strings.HasPrefix(v, arg) { + return strings.TrimPrefix(v, arg) + } + } + for _, v := range s.cmds { + if strings.HasPrefix(v, arg) { + return strings.TrimPrefix(v, arg) + } + } + return tip +} +func (s iterm) rest(res string) string { + s.tip = s.tips(s.arg + s.app) + return res + ESC_K + ESC_s + s.app + s.style("2", s.tip+s.due) + ESC_u +} +func (s iterm) exec(res string) string { + res += CRNL + arg := kit.Split(s.arg + s.app) + if len(arg) == 0 { + return res + s.prompt() + } else if len(s.list) == 0 || s.arg+s.app != s.list[len(s.list)-1] { + s.list = append(s.list, s.arg+s.app) + s.pos = len(s.list) + } + defer func() { s.arg, s.app, s.due, s.args = "", "", "", []string{} }() + msg := s.m.Cmd(arg, kit.Dict(ice.MSG_USERUA, "ish")) + kit.If(msg.IsErrNotFound(), func() { msg = s.m.Cmd(cli.SYSTEM, arg) }) + kit.If(msg.Result() == "", func() { msg.TableEcho() }) + res += ESC_K + strings.ReplaceAll(msg.Result(), lex.NL, CRNL) + kit.If(!strings.HasSuffix(res, CRNL), func() { res += CRNL }) + return res + s.prompt() +} +func (s iterm) hist(res string, skip int) string { + res += s.repeat(s.arg) + ESC_K + kit.If(s.pos == len(s.list), func() { s.bak = s.arg }) + for s.history(skip); s.pos < len(s.list); s.history(skip) { + if strings.Contains(s.list[s.pos], s.bak) { + s.arg, s.app = s.list[s.pos], "" + res += s.list[s.pos] + return res + } + } + s.arg, s.app = s.bak, "" + res += s.bak + return res +} +func (s iterm) history(n int) { s.pos = (s.pos + n + len(s.list) + 1) % (len(s.list) + 1) } + +// AEBF UKHD ITWY JMCZ NPRS LGVQ XO +const ( + NUL = "\000" + SOH = "\001" // Ctrl+A + STX = "\002" // Ctrl+B + ETX = "\003" // Ctrl+C + EOT = "\004" // Ctrl+D + ENQ = "\005" // Ctrl+E + ACK = "\006" // Ctrl+F + BEL = "\007" // Ctrl+G + BS = "\010" // Ctrl+H + HT = "\011" // Ctrl+I + NL = "\012" // Ctrl+J + VT = "\013" // Ctrl+K + NP = "\014" // Ctrl+L + CR = "\015" // Ctrl+M + SO = "\016" // Ctrl+N + SI = "\017" // Ctrl+O + DLE = "\020" // Ctrl+P + DC1 = "\021" // Ctrl+Q + DC2 = "\022" // Ctrl+R + DC3 = "\023" // Ctrl+S + DC4 = "\024" // Ctrl+T + NAK = "\025" // Ctrl+U + SYN = "\026" // Ctrl+V + ETB = "\027" // Ctrl+W + CAN = "\030" // Ctrl+X + EM = "\031" // Ctrl+Y + SUB = "\032" // Ctrl+Z + ESC = "\033" // Ctrl+[ + ESC_C = "\033[C" + ESC_K = "\033[K" + ESC_H = "\033[H" + ESC_2J = "\033[2J" + ESC_s = "\033[s" + ESC_u = "\033[u" + DEL = "\177" + CRNL = "\r\n" +) diff --git a/misc/xterm/xterm.go b/misc/xterm/xterm.go index 9d2d7afa..b238a6e1 100644 --- a/misc/xterm/xterm.go +++ b/misc/xterm/xterm.go @@ -17,33 +17,39 @@ type Winsize struct { Y uint16 // ws_ypixel: Height in pixels } -type XTerm struct { +type XTerm interface { + Setsize(rows, cols string) error + Writeln(data string, arg ...ice.Any) + Write(data string) (int, error) + Read(buf []byte) (int, error) + Close() error +} +type xterm struct { *exec.Cmd *os.File } -func (s XTerm) Setsize(rows, cols string) error { +func (s xterm) Setsize(rows, cols string) error { return Setsize(s.File, &Winsize{Rows: uint16(kit.Int(rows)), Cols: uint16(kit.Int(cols))}) } -func (s XTerm) Writeln(data string, arg ...ice.Any) { - s.Write(kit.Format(data, arg...) + lex.NL) -} -func (s XTerm) Write(data string) (int, error) { - return s.File.Write([]byte(data)) -} -func (s XTerm) Close() error { - return s.Cmd.Process.Kill() -} -func Command(m *ice.Message, dir string, cli string, arg ...string) (*XTerm, error) { +func (s xterm) Writeln(data string, arg ...ice.Any) { s.Write(kit.Format(data, arg...) + lex.NL) } +func (s xterm) Write(data string) (int, error) { return s.File.Write([]byte(data)) } +func (s xterm) Read(buf []byte) (int, error) { return s.File.Read(buf) } +func (s xterm) Close() error { return s.Cmd.Process.Kill() } + +func Command(m *ice.Message, dir string, cli string, arg ...string) (XTerm, error) { + if cli == "ish" { + return newiterm(m) + } cmd := exec.Command(cli, arg...) cmd.Dir = nfs.MkdirAll(m, kit.Path(dir)) cmd.Env = append(cmd.Env, os.Environ()...) cmd.Env = append(cmd.Env, "TERM=xterm") - pty, tty, err := Open() - if err != nil { + if pty, tty, err := Open(); err != nil { return nil, err + } else { + Setsid(cmd) + cmd.Stdin, cmd.Stdout, cmd.Stderr = tty, tty, tty + return &xterm{cmd, pty}, cmd.Start() } - cmd.Stdin, cmd.Stdout, cmd.Stderr = tty, tty, tty - Setsid(cmd) - return &XTerm{cmd, pty}, cmd.Start() }