action.go 15 KB

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