terminal_handler.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. package hhccli
  2. import (
  3. "bufio"
  4. "context"
  5. "fmt"
  6. "git.swzry.com/zry/go-hhc-cli/hhc_ast"
  7. "git.swzry.com/zry/go-hhc-cli/hhc_common"
  8. "git.swzry.com/zry/ztermtab"
  9. "io"
  10. "runtime"
  11. "unicode"
  12. "unicode/utf8"
  13. )
  14. const (
  15. TermInteractiveLogLevel_Fatal TerminalInteractiveDebugLogLevel = 0
  16. TermInteractiveLogLevel_Error TerminalInteractiveDebugLogLevel = 1
  17. TermInteractiveLogLevel_Warn TerminalInteractiveDebugLogLevel = 2
  18. TermInteractiveLogLevel_Info TerminalInteractiveDebugLogLevel = 3
  19. TermInteractiveLogLevel_Debug TerminalInteractiveDebugLogLevel = 4
  20. )
  21. func (lv TerminalInteractiveDebugLogLevel) Name() string {
  22. switch lv {
  23. case TermInteractiveLogLevel_Fatal:
  24. return "FATAL"
  25. case TermInteractiveLogLevel_Error:
  26. return "ERROR"
  27. case TermInteractiveLogLevel_Warn:
  28. return "WARN"
  29. case TermInteractiveLogLevel_Info:
  30. return "INFO"
  31. case TermInteractiveLogLevel_Debug:
  32. return "DEBUG"
  33. default:
  34. return "???"
  35. }
  36. }
  37. type TerminalInteractiveDebugLogLevel uint8
  38. type TerminalInteractiveDebugLog func(lv TerminalInteractiveDebugLogLevel, msg ...interface{})
  39. type TerminalInteractive struct {
  40. inStream io.Reader
  41. outStream io.Writer
  42. errStream io.Writer
  43. debugWrite TerminalInteractiveDebugLog
  44. termWidth int
  45. inBufReader *bufio.Reader
  46. errCh chan error
  47. runeCh chan rune
  48. printResultRuneCh chan rune
  49. currentPrompt string
  50. views map[string]CliView
  51. currentView CliView
  52. termState hhc_common.TerminalState
  53. parser *hhc_ast.SDTLineParser
  54. runesEditor *hhc_common.RunesEditor
  55. backspaceRune rune
  56. tcesParser *SimpleTCESParser
  57. printModePrintLines []string
  58. printModeLineCount int
  59. printModeCurrentLine int
  60. printModeIsHelp bool
  61. execModeContext *hhc_ast.ExecContext
  62. }
  63. type TerminalHandlerInterface interface {
  64. Reset(iow io.Writer, promptStr string, sdtp *hhc_ast.SDTLineParser)
  65. ProcessRune(r rune) (end bool, reprint bool)
  66. GetCmdLinePrintable(termWidth int) []string
  67. GetCalculatedCleanLineCount() int
  68. }
  69. type TerminalInteractiveConfig struct {
  70. InputStream io.Reader
  71. OutputStream io.Writer
  72. ErrorStream io.Writer
  73. InitialTerminalWidth int
  74. DebugLogFunction TerminalInteractiveDebugLog
  75. InitialPrompt string
  76. BackspaceRune rune
  77. }
  78. func nullDebugLogFunc(lv TerminalInteractiveDebugLogLevel, msg ...interface{}) {
  79. }
  80. func NewTerminalInteractive(cfg TerminalInteractiveConfig) *TerminalInteractive {
  81. ti := &TerminalInteractive{
  82. inStream: cfg.InputStream,
  83. outStream: cfg.OutputStream,
  84. errStream: cfg.ErrorStream,
  85. termWidth: cfg.InitialTerminalWidth,
  86. inBufReader: bufio.NewReader(cfg.InputStream),
  87. currentPrompt: cfg.InitialPrompt,
  88. views: map[string]CliView{},
  89. termState: hhc_common.TerminalState_Idle,
  90. backspaceRune: cfg.BackspaceRune,
  91. tcesParser: NewSimpleTCESParser(),
  92. }
  93. if cfg.DebugLogFunction == nil {
  94. ti.debugWrite = nullDebugLogFunc
  95. } else {
  96. ti.debugWrite = cfg.DebugLogFunction
  97. }
  98. if ti.currentPrompt == "" {
  99. ti.currentPrompt = ">"
  100. }
  101. if ti.backspaceRune == 0 {
  102. ti.backspaceRune = '\x7F'
  103. }
  104. return ti
  105. }
  106. func (ti *TerminalInteractive) RegisterView(viewClassName string, view CliView) {
  107. ti.views[viewClassName] = view
  108. }
  109. func (ti *TerminalInteractive) SetCurrentView(viewClassName string) error {
  110. v, ok := ti.views[viewClassName]
  111. if ok {
  112. ti.currentView = v
  113. } else {
  114. ti.currentView = nil
  115. return fmt.Errorf("invalid vcn")
  116. }
  117. return nil
  118. }
  119. func (ti *TerminalInteractive) readRunesRoutine() {
  120. for {
  121. rr, _, err := ti.inBufReader.ReadRune()
  122. if err != nil {
  123. ti.errCh <- err
  124. return
  125. }
  126. ti.runeCh <- rr
  127. }
  128. }
  129. func (ti *TerminalInteractive) SetPrompt(p string) {
  130. ti.currentPrompt = p
  131. }
  132. func (ti *TerminalInteractive) Run(ctx context.Context) error {
  133. ti.errCh = make(chan error)
  134. ti.runeCh = make(chan rune)
  135. go ti.readRunesRoutine()
  136. err := ti.gotoStateInput(false)
  137. if err != nil {
  138. return err
  139. }
  140. for {
  141. select {
  142. case <-ctx.Done():
  143. ti.debugWrite(TermInteractiveLogLevel_Info, "canceled by user")
  144. return nil
  145. case err := <-ti.errCh:
  146. ti.debugWrite(TermInteractiveLogLevel_Error, err.Error())
  147. return err
  148. case rr := <-ti.runeCh:
  149. err := ti.stateMachinePushRune(rr)
  150. if err != nil {
  151. return err
  152. }
  153. }
  154. }
  155. }
  156. func (ti *TerminalInteractive) stateMachinePushRune(r rune) error {
  157. for {
  158. switch ti.termState {
  159. case hhc_common.TerminalState_Idle:
  160. return ti.gotoStateInput(false)
  161. case hhc_common.TerminalState_Input:
  162. b, err := ti.stateInputPushRune(r)
  163. if b || err != nil {
  164. return err
  165. }
  166. case hhc_common.TerminalState_TCES:
  167. b, err := ti.stateTCESPushRune(r)
  168. if b || err != nil {
  169. return err
  170. }
  171. case hhc_common.TerminalState_Execute:
  172. if ti.execModeContext != nil {
  173. ti.execModeContext.WriteRune(r)
  174. }
  175. return nil
  176. case hhc_common.TerminalState_PrintResult:
  177. b, err := ti.statePrintResultPushRune(r)
  178. if b || err != nil {
  179. return err
  180. }
  181. default:
  182. return fmt.Errorf("invalid state for terminal handler state machine: %d", ti.termState)
  183. }
  184. }
  185. }
  186. func (ti *TerminalInteractive) gotoStateInput(saveHistory bool) error {
  187. if ti.currentView == nil {
  188. return fmt.Errorf("no current view specified")
  189. }
  190. sdtf := ti.currentView.GetSDTRoot()
  191. sdtw := hhc_ast.NewSDTWalker(sdtf)
  192. ti.parser = hhc_ast.NewSDTLineParser(sdtw)
  193. if ti.runesEditor == nil {
  194. ti.runesEditor = hhc_common.NewRunesEditor()
  195. } else {
  196. ti.runesEditor.ClearCurrentLine(saveHistory)
  197. }
  198. _, _ = fmt.Fprint(ti.outStream, "\r\n")
  199. ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
  200. ti.termState = hhc_common.TerminalState_Input
  201. return nil
  202. }
  203. func (ti *TerminalInteractive) gotoStateTCES() error {
  204. ti.termState = hhc_common.TerminalState_TCES
  205. ti.tcesParser.ResetState()
  206. return nil
  207. }
  208. func (ti *TerminalInteractive) gotoStatePrintResult(s []string, isHelp bool) {
  209. ti.printModeIsHelp = isHelp
  210. ti.printModeLineCount = len(s)
  211. ti.printModeCurrentLine = 0
  212. ti.printModePrintLines = s
  213. ti.termState = hhc_common.TerminalState_PrintResult
  214. _, _ = fmt.Fprint(ti.errStream, "---- More ----")
  215. ti.printLinesInPrintResultState(20)
  216. }
  217. func (ti *TerminalInteractive) gotoStateExecute(ef hhc_ast.ExecuteFunc, ctx *hhc_ast.SDTWalkContext) {
  218. ti.termState = hhc_common.TerminalState_Execute
  219. ti.execModeContext = hhc_ast.WrapNewExecContext(ctx, ti.outStream, ti.errStream, ti.termWidth)
  220. go ti.executeRoutine(ef)
  221. }
  222. func (ti *TerminalInteractive) exitStateExecute() {
  223. if ti.execModeContext != nil {
  224. nv := ti.execModeContext.GetNextView()
  225. if nv != "" {
  226. err := ti.SetCurrentView(nv)
  227. if err != nil {
  228. _, _ = fmt.Fprintf(ti.errStream, "\r\n %% command line system error in change view: %s\r\n", err.Error())
  229. }
  230. }
  231. wcp, np := ti.execModeContext.GetNextPrompt()
  232. if wcp {
  233. ti.SetPrompt(np)
  234. }
  235. if ti.execModeContext.WillGotoPrintResultMode() {
  236. res := ti.execModeContext.GetResultPrintLines()
  237. _, _ = fmt.Fprintf(ti.errStream, "\r\n")
  238. ti.gotoStatePrintResult(res, false)
  239. } else {
  240. err := ti.gotoStateInput(false)
  241. if err != nil {
  242. _, _ = fmt.Fprintf(ti.errStream, "\r\n %% command line system error: %s\r\n", err.Error())
  243. }
  244. }
  245. ti.execModeContext = nil
  246. } else {
  247. _, _ = fmt.Fprintf(ti.errStream, "\r\n %% broken command (this is a bug... or may be a feature?)\r\n")
  248. err := ti.gotoStateInput(false)
  249. if err != nil {
  250. _, _ = fmt.Fprintf(ti.errStream, "\r\n %% command line system error: %s\r\n", err.Error())
  251. }
  252. }
  253. }
  254. func (ti *TerminalInteractive) executeRoutine(ef hhc_ast.ExecuteFunc) {
  255. if ti.execModeContext != nil {
  256. if ef != nil {
  257. err := ti.protectedExecute(ef, ti.execModeContext)
  258. if err != nil {
  259. _, _ = fmt.Fprintf(ti.errStream, "\r\n %% broken command (this is a bug... or may be a feature?)\r\n")
  260. _, _ = fmt.Fprintf(ti.errStream, "command line execute error: %s\r\n", err.Error())
  261. }
  262. ti.exitStateExecute()
  263. } else {
  264. ti.exitStateExecute()
  265. }
  266. } else {
  267. ti.exitStateExecute()
  268. }
  269. }
  270. func (ti *TerminalInteractive) protectedExecute(ef hhc_ast.ExecuteFunc, ctx *hhc_ast.ExecContext) error {
  271. var err error
  272. defer func() {
  273. rec := recover()
  274. switch rec.(type) {
  275. case runtime.Error:
  276. err = rec.(runtime.Error)
  277. break
  278. case error:
  279. err = rec.(error)
  280. break
  281. }
  282. }()
  283. ef(ctx.GetUserContext())
  284. return err
  285. }
  286. func (ti *TerminalInteractive) printLinesInPrintResultState(nline int) {
  287. if ti.printModePrintLines == nil {
  288. if ti.printModeIsHelp {
  289. ti.termState = hhc_common.TerminalState_Input
  290. ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
  291. return
  292. } else {
  293. _ = ti.gotoStateInput(true)
  294. return
  295. }
  296. }
  297. isend := false
  298. _, _ = fmt.Fprint(ti.errStream, "\r\033[K")
  299. for i := 0; i < nline; i++ {
  300. if ti.printModeCurrentLine < ti.printModeLineCount {
  301. _, _ = fmt.Fprintf(ti.errStream, " %s\r\n", ti.printModePrintLines[ti.printModeCurrentLine])
  302. ti.printModeCurrentLine++
  303. }
  304. if ti.printModeCurrentLine == ti.printModeLineCount {
  305. isend = true
  306. break
  307. }
  308. }
  309. if isend {
  310. _, _ = fmt.Fprint(ti.errStream, "\r\n")
  311. ti.printModePrintLines = nil
  312. if ti.printModeIsHelp {
  313. ti.termState = hhc_common.TerminalState_Input
  314. ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
  315. return
  316. } else {
  317. _ = ti.gotoStateInput(true)
  318. return
  319. }
  320. } else {
  321. _, _ = fmt.Fprint(ti.errStream, "---- More ----")
  322. }
  323. }
  324. func (ti *TerminalInteractive) stateInputPushRune(r rune) (bool, error) {
  325. if utf8.ValidRune(r) {
  326. if unicode.IsControl(r) {
  327. return ti.stateInputPushControlRune(r)
  328. }
  329. if unicode.IsGraphic(r) {
  330. return ti.stateInputPushGraphicRune(r)
  331. }
  332. }
  333. return true, nil
  334. }
  335. func (ti *TerminalInteractive) stateInputPushControlRune(r rune) (bool, error) {
  336. switch r {
  337. case '\x03':
  338. err := ti.gotoStateInput(false)
  339. if err != nil {
  340. return true, err
  341. }
  342. return true, nil
  343. case ti.backspaceRune:
  344. if ti.runesEditor != nil {
  345. ti.runesEditor.Backspace()
  346. ti.runesEditor.RefreshTerminal(ti.outStream, ti.currentPrompt, ti.termWidth, false)
  347. }
  348. return true, nil
  349. case '\x1b':
  350. err := ti.gotoStateTCES()
  351. if err != nil {
  352. return true, err
  353. }
  354. return true, nil
  355. case '\x09':
  356. ti.parser.Reset()
  357. rs := ti.runesEditor.Peek()
  358. ti.parser.Parse(rs)
  359. if ti.parser.HasError() {
  360. return true, nil
  361. }
  362. hi, tok := ti.parser.GetHelpListForTabComplete()
  363. if len(hi) == 1 {
  364. if hi[0].IsArg {
  365. return true, nil
  366. }
  367. suf := hhc_common.GetCompleteSuffix(hi[0].Name, tok)
  368. ti.runesEditor.InsertString(suf)
  369. ti.runesEditor.RefreshTerminal(ti.outStream, ti.currentPrompt, ti.termWidth, false)
  370. } else {
  371. ttp := ztermtab.NewTerminalTablePrinter(ti.termWidth, " ", " | ", "")
  372. ttp.AddColumn("", 10, 30, false)
  373. ttp.AddColumn("", 0, 0, false)
  374. ttp.SetAutoWidthColumn(1)
  375. for _, v := range hi {
  376. ttp.AddRow([]string{v.Name, v.Description})
  377. }
  378. _, _ = fmt.Fprint(ti.errStream, "\r\n")
  379. ti.gotoStatePrintResult(ttp.RenderToTerminalLines(), true)
  380. }
  381. return true, nil
  382. case '\r':
  383. if len(ti.runesEditor.Peek()) <= 0 {
  384. _, _ = fmt.Fprintf(ti.errStream, "\r\n")
  385. ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
  386. return true, nil
  387. }
  388. ti.parser.Reset()
  389. rs := ti.runesEditor.Enter()
  390. ti.parser.Parse(rs)
  391. ok, ef := ti.parser.TryGetRunFunc()
  392. if ti.parser.HasError() {
  393. e, p := ti.parser.GetError()
  394. _, _ = fmt.Fprintf(ti.errStream, "\r\n %% %s at position %d\r\n", hhc_common.FirstToUpper(e.EES()), p)
  395. ti.termState = hhc_common.TerminalState_Input
  396. ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
  397. return true, nil
  398. }
  399. if ok {
  400. if ef == nil {
  401. _, _ = fmt.Fprintf(ti.errStream, "\r\n %% broken command (this is a bug... or may be a feature?)\r\n")
  402. ti.termState = hhc_common.TerminalState_Input
  403. ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
  404. return true, nil
  405. } else {
  406. ti.gotoStateExecute(ef, ti.parser.GetWalkContext())
  407. return true, nil
  408. }
  409. } else {
  410. _, _ = fmt.Fprintf(ti.errStream, "\r\n %% incomplete command\r\n")
  411. ti.termState = hhc_common.TerminalState_Input
  412. ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
  413. return true, nil
  414. }
  415. }
  416. return true, nil
  417. }
  418. func (ti *TerminalInteractive) stateInputPushGraphicRune(r rune) (bool, error) {
  419. if ti.runesEditor != nil {
  420. ti.runesEditor.InsertRune(r)
  421. ti.runesEditor.RefreshTerminal(ti.outStream, ti.currentPrompt, ti.termWidth, false)
  422. }
  423. if r == '?' {
  424. ti.parser.Reset()
  425. rs := ti.runesEditor.Peek()
  426. ti.parser.Parse(rs)
  427. if !ti.parser.HelpTriggerd() {
  428. return true, nil
  429. }
  430. _, _ = fmt.Fprint(ti.errStream, "\r\n")
  431. if ti.parser.HasError() {
  432. e, p := ti.parser.GetError()
  433. _, _ = fmt.Fprintf(ti.errStream, " %% %s at position %d\r\n", hhc_common.FirstToUpper(e.EES()), p)
  434. ti.runesEditor.Backspace()
  435. ti.termState = hhc_common.TerminalState_Input
  436. ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
  437. return true, nil
  438. }
  439. if ti.parser.WillPrintHelp() {
  440. ti.runesEditor.Backspace()
  441. hi := ti.parser.GetHelpMessage()
  442. ttp := ztermtab.NewTerminalTablePrinter(ti.termWidth, " ", " | ", "")
  443. ttp.AddColumn("", 10, 30, false)
  444. ttp.AddColumn("", 0, 0, false)
  445. ttp.SetAutoWidthColumn(1)
  446. for _, v := range hi {
  447. ttp.AddRow([]string{v.Name, v.Description})
  448. }
  449. ti.gotoStatePrintResult(ttp.RenderToTerminalLines(), true)
  450. return true, nil
  451. }
  452. }
  453. return true, nil
  454. }
  455. func (ti *TerminalInteractive) stateTCESPushRune(r rune) (bool, error) {
  456. b, res := ti.tcesParser.PushRune(r)
  457. if b {
  458. ti.termState = hhc_common.TerminalState_Input
  459. if ti.runesEditor == nil {
  460. return true, nil
  461. }
  462. switch res {
  463. case SimpleTCESParserResult_LeftArrow:
  464. ti.runesEditor.MoveInsertPosition(-1)
  465. break
  466. case SimpleTCESParserResult_RightArrow:
  467. ti.runesEditor.MoveInsertPosition(1)
  468. break
  469. case SimpleTCESParserResult_UpArrow:
  470. ti.runesEditor.LoadPreviousHistory()
  471. break
  472. case SimpleTCESParserResult_DownArrow:
  473. ti.runesEditor.LoadNextHistory()
  474. break
  475. case SimpleTCESParserResult_HomeKey:
  476. ti.runesEditor.MoveInsertPositionToHome()
  477. break
  478. case SimpleTCESParserResult_EndKey:
  479. ti.runesEditor.MoveInsertPositionToEnd()
  480. break
  481. case SimpleTCESParserResult_DelKey:
  482. ti.runesEditor.Delete()
  483. break
  484. default:
  485. break
  486. }
  487. ti.runesEditor.RefreshTerminal(ti.outStream, ti.currentPrompt, ti.termWidth, false)
  488. }
  489. return true, nil
  490. }
  491. func (ti *TerminalInteractive) statePrintResultPushRune(r rune) (bool, error) {
  492. switch r {
  493. case '\r':
  494. ti.printLinesInPrintResultState(1)
  495. break
  496. case ' ':
  497. ti.printLinesInPrintResultState(20)
  498. break
  499. case '\x03':
  500. _, _ = fmt.Fprint(ti.errStream, "\r\n")
  501. ti.printModePrintLines = nil
  502. if ti.printModeIsHelp {
  503. ti.termState = hhc_common.TerminalState_Input
  504. ti.runesEditor.PeekIntoTerminal(ti.currentPrompt, ti.termWidth, ti.outStream)
  505. } else {
  506. _ = ti.gotoStateInput(true)
  507. }
  508. break
  509. }
  510. return true, nil
  511. }
  512. func (ti *TerminalInteractive) SetTerminalWidth(w int) {
  513. ti.termWidth = w
  514. }
  515. func (ti *TerminalInteractive) GetTerminalWidth() int {
  516. return ti.termWidth
  517. }