πŸ“œ ⬆️ ⬇️

Menu for Yi

Recently, I decided to sit down and deal with Yi , a text editor like Vim and Emacs, but written in Haskell. Included is even a Vim and Emacs simulation.
Due to the lack of experience with Vim or Emacs, only Cua-simulation came to me. Hotkeev there is little, but they are familiar to me. So I decided to start with it and write the setting for myself.
In ordinary graphic editors, it seems to me a convenient way to use the menu. You press alt, a menu opens, where each element has a letter underlined, clicking which we will select this element.
Thus, it is not necessary to memorize all the commands at once, but you can start using, peeping in the menu, gradually bringing it to automatism.
I decided to screw something like this in Yi.

image


Customize simple hotkeys


First you need to figure out how Yi works? The easiest way to understand this, if you look at the already prepared bindings, such as Cua . It is trimmed, and much more primitive than Vim and Emacs binding analogs, but for our purposes (to write our own) is the very thing.
')
First of all, let's pay attention to how hotkeys are set at all. This can be seen from the main function.
keymap :: KeymapSet keymap = portableKeymap ctrl -- | Introduce a keymap that is compatible with both windows and osx, -- by parameterising the event modifier required for commands portableKeymap :: (Event -> Event) -> KeymapSet portableKeymap cmd = modelessKeymapSet $ selfInsertKeymap <|> move <|> select <|> rect <|> other cmd 

Different options for binding are combined with the help of the operator <|>. Let's look further at the other cmd:
 other cmd = choice [ spec KBS ?>>! deleteSel bdeleteB, spec KDel ?>>! deleteSel (deleteN 1), spec KEnter ?>>! replaceSel "\n", cmd (char 'q') ?>>! askQuitEditor, cmd (char 'f') ?>> isearchKeymap Forward, cmd (char 'x') ?>>! cut, cmd (char 'c') ?>>! copy, cmd (char 'v') ?>>! paste, cmd (spec KIns) ?>>! copy, shift (spec KIns) ?>>! paste, cmd (char 'z') ?>>! undoB, cmd (char 'y') ?>>! redoB, cmd (char 's') ?>>! fwriteE, cmd (char 'o') ?>>! findFile, cmd (char '/') ?>>! withModeB modeToggleCommentSelection, cmd (char ']') ?>>! autoIndentB IncreaseOnly, cmd (char '[') ?>>! autoIndentB DecreaseOnly ] 

As you can see, on the left is the key combination, on the right is the action. Those. when you click cmd (char 'c') (by default cmd - ctrl) - we get copy, the code of which is also plain.

I copied these definitions to me and decided to start editing them in order to build some sort of menu.

How to make a menu?


To decide how to implement the menu, you should go to the documentation modules . Everything is structured quite comfortably, and the Yi.MiniBuffer module catches the eye . Apparently, this is what we need. There is a function
 spawnMinibufferE :: String -> KeymapEndo -> EditorM BufferRef 

which accepts output text and a function that puts its bindings on the keys. Those. what we need. In the line we will display the menu items, in the binders we will catch the selection of menu items by keys.

To begin, create a type convenient for describing the menu. The menu consists of a list of items, each of which either opens a submenu or is an action. So we write:
 -- | Menu type Menu = [MenuItem] -- | Menu utem data MenuItem = MenuAction String (MenuContext -> Char -> Keymap) | SubMenu String Menu -- | Menu action context data MenuContext = MenuContext { parentBuffer :: BufferRef } 

The SubMenu variant contains the title and submenus, the MenuAction option is the title and the function that will create the necessary bindings.
MenuContext is some context that is passed to actions (as long as there is only the source buffer from which the menu was called, this was needed to implement the Save button), Char is the button that the menu needs to be called upon pressing.

Since the type is recursive, for it you can simply define a convolution, so that later, using it, launch the menu:
 -- | Fold menu item foldItem :: (String -> (MenuContext -> Char -> Keymap) -> a) -> (String -> [a] -> a) -> MenuItem -> a foldItem mA sM (MenuAction title act) = mA title act foldItem mA sM (SubMenu title sm) = sM title (map (foldItem mA sM) sm) -- | Fold menu foldMenu :: (String -> (MenuContext -> Char -> Keymap) -> a) -> (String -> [a] -> a) -> Menu -> [a] foldMenu mA sM = map (foldItem mA sM) 


We also need functions that will more conveniently create menu items for us. SubMenu is easy to create, SubMenu "File" ..., but MenuAction is harder to use. Therefore, we define several functions that will take action (the same as to the right of? >>! In binding). I will give the code of two of them:
 -- | Action on item action_ :: (YiAction ax, Show x) => String -> a -> MenuItem action_ title act = action title (const act) -- | Action on item with context action :: (YiAction ax, Show x) => String -> (MenuContext -> a) -> MenuItem action title act = MenuAction title act' where act' ctx c = char c ?>>! (do withEditor closeBufferAndWindowE runAction $ makeAction (act ctx)) 

Here we create a MenuItem, which when clicked on the corresponding button (char c) will close the menu and invoke the action that we need.

And finally, let's write the menu display function.
 -- | Start menu action startMenu :: Menu -> EditorM () startMenu m = do --  ,   ctx <- fmap MenuContext (gets currentBuffer) startMenu' ctx m where --  ,      (, ) startMenu' ctx = showMenu . foldMenu onItem onSub where showMenu :: [(String, Maybe Keymap)] -> EditorM () --   β€”      ,    showMenu is = void $ spawnMinibufferE menuItems (const (subMap is)) where menuItems = (intercalate " " (map fst is)) --    β€” --   +    ,   onItem title act = (title, fmap (act ctx) (menuEvent title)) where --    β€” --  +  ,        onSub title is = (title, fmap subMenu (menuEvent title)) where --  'c'        subMenu c = char c ?>>! closeBufferAndWindowE >> showMenu is --        Esc,        subMap is = choice $ closeMenu : mapMaybe snd is where closeMenu = spec KEsc ?>>! closeBufferAndWindowE 

The full code can be found here .

Create a menu


Now it’s worth creating some menus, sticking to a button and starting to be checked.
First, I wrote a large spreading menu, stuffing in there what I came across with a cursory view of various modules in Yi. When I noticed that, for example, I often go to the submenu View - Windows, I decided to just bring this menu to a separate hotkey.
Now you can split the window not only by a long combination of VWS, but also just by Ctrl-W - S.

Here is the code for the main menu and submenus of Windows:
 -- | Main menu mainMenu :: Menu mainMenu = [ menu "File" [ action_ "Quit" askQuitEditor, action "Save" (fwriteBufferE . parentBuffer)], menu "Edit" [ action_ "Auto complete" wordComplete, action_ "Completion" completeWordB], menu "Tools" [ menu "Ghci" ghciMenu], menu "View" [ menu "Windows" windowsMenu, menu "Tabs" tabsMenu, menu "Buffers" buffersMenu, menu "Layout" [ action_ "Next" layoutManagersNextE, action_ "Previous" layoutManagersPreviousE]]] -- | Windows menu windowsMenu :: Menu windowsMenu = [ action_ "Next" nextWinE, action_ "Previous" prevWinE, action_ "Split" splitE, action_ "sWap" swapWinWithFirstE, action_ "Close" tryCloseE, action_ "cLose-all-but-this" closeOtherE] 

The entire menu can be viewed here .

Result


We register the main menu and submenu for various combinations.

We use!


Results


After the implementation, I screwed some built-in simplest autocompet, then the GHCi interpreter. The hlint tool is in turn (it analyzes the code and suggests where it can be replaced with the use of a standard function, where something is written and so on) and so on.

All code is available on GitHub .

Update

Somewhat embarrassed by the presence of the three functions actionB , actionE and actionY . However, they can be replaced by runAction . makeAction runAction . makeAction
Fixed for a single action

Source: https://habr.com/ru/post/142769/


All Articles