1
0

action.go 14 KB

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