action.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. package editor
  2. import (
  3. "context"
  4. "moose/internal/about"
  5. "moose/internal/buffer"
  6. "moose/internal/layout"
  7. "moose/internal/util"
  8. "os"
  9. "slices"
  10. "strconv"
  11. "strings"
  12. "github.com/zyedidia/rope"
  13. "golang.design/x/clipboard"
  14. )
  15. type ActionManager = struct {
  16. Common []Action
  17. Normal []Action
  18. Insert []Action
  19. Palette []Action
  20. CM ChordManager
  21. RCM ChordManager
  22. }
  23. type Action = struct {
  24. Bindings []Binding
  25. Commands []string
  26. AskArgs bool
  27. Callback func(*Model, []string)
  28. }
  29. type Binding = struct {
  30. Type BindingType
  31. Single string
  32. Chord []string
  33. }
  34. type BindingType int
  35. const (
  36. BindingSingle BindingType = iota
  37. BindingChord
  38. )
  39. type ChordManager = struct {
  40. Recording bool
  41. Recorded []string
  42. }
  43. func NewChordManager() ChordManager {
  44. return ChordManager{
  45. Recording: false,
  46. Recorded: []string{},
  47. }
  48. }
  49. func DefaultActionManager() ActionManager {
  50. am := ActionManager{
  51. CM: NewChordManager(),
  52. Common: []Action{
  53. Action{
  54. Commands: []string{"bsel"},
  55. AskArgs: true,
  56. Callback: func(m *Model, args []string) {
  57. if len(args) < 1 {
  58. return
  59. }
  60. n, err := strconv.Atoi(args[0])
  61. if err != nil || n < 1 {
  62. return
  63. }
  64. targetTab := n - 1
  65. ws, ok := m.LM.Workspaces[m.LM.ActiveIdx]
  66. if !ok {
  67. return
  68. }
  69. selected := false
  70. ws.RootContainer.WalkAndMutateActive(func(cb *layout.ContainerBuffers) layout.ContainerNode {
  71. if targetTab >= 0 && targetTab < len(cb.Buffers) {
  72. cb.ActiveIdx = targetTab
  73. m.BM.CurrentIdx = cb.Buffers[targetTab]
  74. selected = true
  75. }
  76. return *cb
  77. })
  78. if selected {
  79. m.LM.Workspaces[m.LM.ActiveIdx] = ws
  80. m.Mode = ModeNormal
  81. }
  82. },
  83. },
  84. Action{
  85. Bindings: []Binding{
  86. Binding{
  87. Type: BindingSingle,
  88. Single: "ctrl+tab",
  89. },
  90. },
  91. Callback: func(m *Model, args []string) {
  92. ws, ok := m.LM.Workspaces[m.LM.ActiveIdx]
  93. if !ok {
  94. return
  95. }
  96. ws.RootContainer.WalkAndMutateActive(func(cb *layout.ContainerBuffers) layout.ContainerNode {
  97. if len(cb.Buffers) > 0 {
  98. cb.ActiveIdx = (cb.ActiveIdx + 1) % len(cb.Buffers)
  99. m.BM.CurrentIdx = cb.Buffers[cb.ActiveIdx]
  100. }
  101. return *cb
  102. })
  103. m.LM.Workspaces[m.LM.ActiveIdx] = ws
  104. },
  105. },
  106. Action{
  107. Bindings: []Binding{
  108. Binding{
  109. Type: BindingSingle,
  110. Single: "ctrl+shift+tab",
  111. },
  112. },
  113. Callback: func(m *Model, args []string) {
  114. ws, ok := m.LM.Workspaces[m.LM.ActiveIdx]
  115. if !ok {
  116. return
  117. }
  118. ws.RootContainer.WalkAndMutateActive(func(cb *layout.ContainerBuffers) layout.ContainerNode {
  119. if len(cb.Buffers) > 0 {
  120. cb.ActiveIdx = (cb.ActiveIdx - 1 + len(cb.Buffers)) % len(cb.Buffers)
  121. m.BM.CurrentIdx = cb.Buffers[cb.ActiveIdx]
  122. }
  123. return *cb
  124. })
  125. m.LM.Workspaces[m.LM.ActiveIdx] = ws
  126. },
  127. },
  128. Action{
  129. Commands: []string{"version"},
  130. Callback: func(m *Model, args []string) {
  131. m.BM.PaletteBuffer.Clear()
  132. m.BM.PaletteBuffer.Insert("moose.info:Moose version '" + about.Version + "'")
  133. },
  134. },
  135. Action{
  136. Bindings: []Binding{
  137. Binding{
  138. Type: BindingSingle,
  139. Single: "f12",
  140. },
  141. },
  142. Callback: func(m *Model, args []string) {
  143. m.Mode = ModeNormal
  144. },
  145. },
  146. Action{
  147. Bindings: []Binding{
  148. Binding{
  149. Type: BindingSingle,
  150. Single: "ctrl+q",
  151. },
  152. },
  153. Commands: []string{"q", "quit"},
  154. AskArgs: true,
  155. Callback: func(m *Model, args []string) {
  156. m.Quit()
  157. },
  158. },
  159. Action{
  160. Bindings: []Binding{
  161. Binding{
  162. Type: BindingSingle,
  163. Single: "ctrl+s",
  164. },
  165. },
  166. Commands: []string{"s", "save"},
  167. AskArgs: false,
  168. Callback: func(m *Model, args []string) {
  169. path := ""
  170. if len(args) < 1 || args[0] == "" {
  171. if m.BM.Current().Path == "" {
  172. m.Mode = ModeNormal
  173. m.BM.PaletteBuffer.Clear()
  174. m.BM.PaletteBuffer.Insert("moose.error:Missing filename argument for save")
  175. return
  176. }
  177. }
  178. if len(args) > 0 && args[0] != "" {
  179. m.BM.Current().Path = args[0]
  180. }
  181. path = m.BM.Current().Path
  182. data := []byte(m.BM.Current().String())
  183. err := os.WriteFile(path, data, 0644)
  184. if err != nil {
  185. m.Mode = ModeNormal
  186. m.BM.PaletteBuffer.Clear()
  187. m.BM.PaletteBuffer.Insert("moose.error:Could not save to file \"" + path + "\": " + err.Error())
  188. return
  189. }
  190. m.BM.PaletteBuffer.Clear()
  191. m.BM.PaletteBuffer.Insert("moose.info:Wrote " + strconv.Itoa(len(data)) + " bytes to \"" + path + "\"")
  192. return
  193. },
  194. },
  195. Action{
  196. Bindings: []Binding{
  197. Binding{
  198. Type: BindingSingle,
  199. Single: "ctrl+shift+s",
  200. },
  201. },
  202. Commands: []string{"s", "save"},
  203. AskArgs: true,
  204. },
  205. Action{
  206. Bindings: []Binding{
  207. Binding{
  208. Type: BindingSingle,
  209. Single: "ctrl+e",
  210. },
  211. },
  212. Commands: []string{"e", "edit"},
  213. AskArgs: true,
  214. Callback: func(m *Model, args []string) {
  215. if len(args) < 1 {
  216. m.Mode = ModeNormal
  217. m.BM.PaletteBuffer.Clear()
  218. m.BM.PaletteBuffer.Insert("moose.error:Did not specify path for edit")
  219. return
  220. }
  221. content, err := os.ReadFile(args[0])
  222. if err != nil {
  223. m.Mode = ModeNormal
  224. m.BM.PaletteBuffer.Clear()
  225. m.BM.PaletteBuffer.Insert("moose.error:Could not edit file \"" + args[0] + "\": " + err.Error())
  226. return
  227. }
  228. m.BM.Current().Clear()
  229. m.BM.Current().Rope = rope.New(content)
  230. m.BM.Current().LI.Rebuild(m.BM.Current().Rope)
  231. m.BM.Current().Path = args[0]
  232. m.BM.Current().History = buffer.UndoStack{}
  233. },
  234. },
  235. Action{
  236. Bindings: []Binding{
  237. Binding{
  238. Type: BindingSingle,
  239. Single: "left",
  240. },
  241. },
  242. Callback: func(m *Model, args []string) {
  243. if m.Mode == ModePalette {
  244. m.BM.PaletteBuffer.MoveHoriz(-1)
  245. } else {
  246. m.BM.Current().MoveHoriz(-1)
  247. }
  248. },
  249. },
  250. Action{
  251. Bindings: []Binding{
  252. Binding{
  253. Type: BindingSingle,
  254. Single: "right",
  255. },
  256. },
  257. Callback: func(m *Model, args []string) {
  258. if m.Mode == ModePalette {
  259. m.BM.PaletteBuffer.MoveHoriz(1)
  260. } else {
  261. m.BM.Current().MoveHoriz(1)
  262. }
  263. },
  264. },
  265. Action{
  266. Bindings: []Binding{
  267. Binding{
  268. Type: BindingSingle,
  269. Single: "up",
  270. },
  271. },
  272. Callback: func(m *Model, args []string) {
  273. if m.Mode != ModePalette {
  274. m.BM.Current().MoveVert(-1)
  275. }
  276. },
  277. },
  278. Action{
  279. Bindings: []Binding{
  280. Binding{
  281. Type: BindingSingle,
  282. Single: "down",
  283. },
  284. },
  285. Callback: func(m *Model, args []string) {
  286. if m.Mode != ModePalette {
  287. m.BM.Current().MoveVert(1)
  288. }
  289. },
  290. },
  291. Action{
  292. Bindings: []Binding{
  293. Binding{
  294. Type: BindingSingle,
  295. Single: "shift+left",
  296. },
  297. },
  298. AskArgs: false,
  299. Callback: func(m *Model, args []string) {
  300. if m.Mode == ModePalette {
  301. m.BM.PaletteBuffer.MoveWordHoriz(-1)
  302. } else {
  303. m.BM.Current().MoveWordHoriz(-1)
  304. }
  305. },
  306. },
  307. Action{
  308. Bindings: []Binding{
  309. Binding{
  310. Type: BindingSingle,
  311. Single: "shift+right",
  312. },
  313. },
  314. AskArgs: false,
  315. Callback: func(m *Model, args []string) {
  316. if m.Mode == ModePalette {
  317. m.BM.PaletteBuffer.MoveWordHoriz(1)
  318. } else {
  319. m.BM.Current().MoveWordHoriz(1)
  320. }
  321. },
  322. },
  323. Action{
  324. Bindings: []Binding{
  325. Binding{
  326. Type: BindingChord,
  327. Chord: []string{"ctrl+l", "enter"},
  328. },
  329. },
  330. Commands: []string{"lbuf"},
  331. AskArgs: false,
  332. Callback: func(m *Model, args []string) {
  333. m.AddBuffer(true)
  334. },
  335. },
  336. Action{
  337. Bindings: []Binding{
  338. Binding{
  339. Type: BindingSingle,
  340. Single: "ctrl+left",
  341. },
  342. },
  343. Callback: func(m *Model, args []string) {
  344. m.CycleActiveBuffer(-1.0, 0.0)
  345. },
  346. },
  347. Action{
  348. Bindings: []Binding{
  349. Binding{
  350. Type: BindingSingle,
  351. Single: "ctrl+v",
  352. },
  353. },
  354. Callback: func(m *Model, args []string) {
  355. if m.LM.CurrentSplit == layout.SplitHorizontal {
  356. m.LM.CurrentSplit = layout.SplitVertical
  357. } else {
  358. m.LM.CurrentSplit = layout.SplitHorizontal
  359. }
  360. },
  361. },
  362. Action{
  363. Bindings: []Binding{
  364. Binding{
  365. Type: BindingSingle,
  366. Single: "ctrl+down",
  367. },
  368. },
  369. Callback: func(m *Model, args []string) {
  370. m.CycleActiveBuffer(0.0, 1.0)
  371. },
  372. },
  373. Action{
  374. Bindings: []Binding{
  375. Binding{
  376. Type: BindingSingle,
  377. Single: "ctrl+up",
  378. },
  379. },
  380. Callback: func(m *Model, args []string) {
  381. m.CycleActiveBuffer(0.0, -1.0)
  382. },
  383. },
  384. Action{
  385. Bindings: []Binding{
  386. Binding{
  387. Type: BindingSingle,
  388. Single: "ctrl+right",
  389. },
  390. },
  391. Callback: func(m *Model, args []string) {
  392. m.CycleActiveBuffer(1.0, 0.0)
  393. },
  394. },
  395. },
  396. Normal: []Action{
  397. Action{
  398. Bindings: []Binding{
  399. Binding{
  400. Type: BindingSingle,
  401. Single: "z",
  402. },
  403. },
  404. Commands: []string{"u", "undo"},
  405. AskArgs: false,
  406. Callback: func(m *Model, args []string) {
  407. if m.Mode == ModePalette {
  408. m.BM.PaletteBuffer.Undo()
  409. } else {
  410. m.BM.Current().Undo()
  411. }
  412. },
  413. },
  414. Action{
  415. Bindings: []Binding{
  416. Binding{
  417. Type: BindingSingle,
  418. Single: "y",
  419. },
  420. },
  421. Commands: []string{"r", "redo"},
  422. AskArgs: false,
  423. Callback: func(m *Model, args []string) {
  424. if m.Mode == ModePalette {
  425. m.BM.PaletteBuffer.Redo()
  426. } else {
  427. m.BM.Current().Redo()
  428. }
  429. },
  430. },
  431. Action{
  432. Bindings: []Binding{
  433. Binding{
  434. Type: BindingChord,
  435. Chord: []string{"d", "d"},
  436. },
  437. },
  438. Callback: func(m *Model, args []string) {
  439. m.BM.Current().DeleteLine()
  440. },
  441. },
  442. Action{
  443. Bindings: []Binding{
  444. Binding{
  445. Type: BindingSingle,
  446. Single: "w",
  447. },
  448. },
  449. Callback: func(m *Model, args []string) {
  450. m.Mode = ModeWrite
  451. },
  452. },
  453. Action{
  454. Bindings: []Binding{
  455. Binding{
  456. Type: BindingSingle,
  457. Single: "q",
  458. },
  459. },
  460. Callback: func(m *Model, args []string) {
  461. m.Mode = ModePalette
  462. m.BM.PaletteBuffer.Clear()
  463. m.BM.PaletteBuffer.Insert("/")
  464. },
  465. },
  466. Action{
  467. Bindings: []Binding{
  468. Binding{
  469. Type: BindingSingle,
  470. Single: "f",
  471. },
  472. },
  473. AskArgs: true,
  474. Callback: func(m *Model, args []string) {
  475. m.Mode = ModePalette
  476. m.BM.PaletteBuffer.Clear()
  477. m.BM.PaletteBuffer.Insert("?")
  478. },
  479. },
  480. Action{
  481. Bindings: []Binding{
  482. Binding{
  483. Type: BindingSingle,
  484. Single: "v",
  485. },
  486. },
  487. Commands: []string{"p", "paste"},
  488. AskArgs: false,
  489. Callback: func(m *Model, args []string) {
  490. text, err := clipboard.Read(context.Background(), clipboard.FmtText)
  491. if err != nil {
  492. m.DebugLog = "moose.error:Clipboard read failed: " + err.Error()
  493. return
  494. }
  495. if s := string(text); s != "" {
  496. if m.Mode == ModePalette {
  497. m.BM.PaletteBuffer.Insert(s)
  498. } else {
  499. m.BM.Current().Insert(s)
  500. }
  501. }
  502. },
  503. },
  504. Action{
  505. Bindings: []Binding{
  506. Binding{
  507. Type: BindingSingle,
  508. Single: "enter",
  509. },
  510. },
  511. Commands: []string{"buf"},
  512. AskArgs: false,
  513. Callback: func(m *Model, args []string) {
  514. m.AddBuffer(false)
  515. },
  516. },
  517. Action{
  518. Bindings: []Binding{
  519. Binding{
  520. Type: BindingSingle,
  521. Single: "ctrl+x",
  522. },
  523. },
  524. Callback: func(m *Model, args []string) {
  525. m.RemoveActiveBuffer()
  526. },
  527. },
  528. },
  529. Insert: []Action{
  530. Action{
  531. Bindings: []Binding{
  532. Binding{
  533. Type: BindingSingle,
  534. Single: "shift+up",
  535. },
  536. },
  537. Commands: []string{"curup"},
  538. Callback: func(m *Model, args []string) {
  539. m.BM.Current().AddCursorVert(-1)
  540. },
  541. },
  542. Action{
  543. Bindings: []Binding{
  544. Binding{
  545. Type: BindingSingle,
  546. Single: "shift+down",
  547. },
  548. },
  549. Commands: []string{"curdown"},
  550. Callback: func(m *Model, args []string) {
  551. m.BM.Current().AddCursorVert(1)
  552. },
  553. },
  554. Action{
  555. Bindings: []Binding{
  556. Binding{
  557. Type: BindingSingle,
  558. Single: "esc",
  559. },
  560. },
  561. Commands: []string{"clrc", "clearcursors"},
  562. Callback: func(m *Model, args []string) {
  563. m.BM.Current().ClearCursors()
  564. },
  565. },
  566. Action{
  567. Bindings: []Binding{
  568. Binding{
  569. Type: BindingSingle,
  570. Single: "tab",
  571. },
  572. },
  573. Callback: func(m *Model, args []string) {
  574. for _ = range 4 {
  575. m.BM.Current().Insert(" ")
  576. }
  577. },
  578. },
  579. Action{
  580. Bindings: []Binding{
  581. Binding{
  582. Type: BindingSingle,
  583. Single: "shift+tab",
  584. },
  585. },
  586. Callback: func(m *Model, args []string) {
  587. // TODO: implement m.BM.Current().remove...
  588. },
  589. },
  590. Action{
  591. Bindings: []Binding{
  592. Binding{
  593. Type: BindingSingle,
  594. Single: "backspace",
  595. },
  596. },
  597. Callback: func(m *Model, args []string) {
  598. m.BM.Current().Delete()
  599. },
  600. },
  601. Action{
  602. Bindings: []Binding{
  603. Binding{
  604. Type: BindingSingle,
  605. Single: "enter",
  606. },
  607. },
  608. Callback: func(m *Model, args []string) {
  609. m.BM.Current().Insert("\n")
  610. },
  611. },
  612. },
  613. Palette: []Action{
  614. Action{
  615. Bindings: []Binding{
  616. Binding{
  617. Type: BindingSingle,
  618. Single: "backspace",
  619. },
  620. },
  621. Callback: func(m *Model, args []string) {
  622. m.BM.PaletteBuffer.Delete()
  623. },
  624. },
  625. Action{
  626. Bindings: []Binding{
  627. Binding{
  628. Type: BindingSingle,
  629. Single: "enter",
  630. },
  631. },
  632. Callback: func(m *Model, _ []string) {
  633. input := m.BM.PaletteBuffer.String()
  634. args := strings.Split(input, " ")
  635. if args[0] == "" {
  636. m.Mode = ModeNormal
  637. return
  638. }
  639. cmd := string([]rune(args[0])[1:])
  640. if strings.HasPrefix(args[0], "/") {
  641. for _, action := range append(m.AM.Common, append(m.AM.Normal, m.AM.Insert...)...) {
  642. if action.Callback == nil {
  643. continue
  644. }
  645. if len(action.Commands) > 0 && slices.Contains(util.StandardizeBindingsArray(action.Commands), util.StandardizeBinding(cmd)) {
  646. m.Mode = ModeNormal
  647. action.Callback(m, args[1:])
  648. return
  649. }
  650. }
  651. m.Mode = ModeNormal
  652. m.BM.PaletteBuffer.Clear()
  653. m.BM.PaletteBuffer.Insert("moose.error:Unknown command \"" + cmd + "\"")
  654. } else if strings.HasPrefix(args[0], "?") {
  655. m.Mode = ModeFind
  656. m.BM.PaletteBuffer.Clear()
  657. m.BM.PaletteBuffer.Insert("moose.error:Find mode unimplemented \"" + cmd + "\"")
  658. } else {
  659. m.Mode = ModeNormal
  660. m.BM.PaletteBuffer.Clear()
  661. m.BM.PaletteBuffer.Insert("moose.error:Unknown palette input \"" + args[0] + "\"")
  662. }
  663. },
  664. },
  665. },
  666. }
  667. for i := range 9 {
  668. am.Common = append(am.Common, Action{
  669. Bindings: []Binding{
  670. Binding{
  671. Type: BindingSingle,
  672. Single: "ctrl+" + strconv.Itoa(i+1),
  673. },
  674. },
  675. Callback: func(m *Model, args []string) {
  676. m.Mode = ModeNormal
  677. m.LM.ActiveIdx = i
  678. if bufIdx := m.LM.GetActiveBufferIdx(); bufIdx >= 0 && bufIdx < len(m.BM.Buffers) {
  679. m.BM.CurrentIdx = bufIdx
  680. } else if len(m.BM.Buffers) > 0 {
  681. m.BM.CurrentIdx = 0
  682. }
  683. },
  684. })
  685. }
  686. return am
  687. }