├── packages ├── app │ ├── changelog.md │ ├── app │ │ └── Main.hs │ ├── readme.md │ ├── lib │ │ └── Ribosome │ │ │ └── App │ │ │ ├── Data │ │ │ └── TemplateTree.hs │ │ │ ├── Templates │ │ │ ├── MainHs.hs │ │ │ ├── TestMainHs.hs │ │ │ ├── PingTestHs.hs │ │ │ ├── PluginHs.hs │ │ │ └── ReadmeMd.hs │ │ │ ├── ProjectPath.hs │ │ │ ├── ProjectNames.hs │ │ │ ├── Cli.hs │ │ │ ├── NewOptions.hs │ │ │ ├── Error.hs │ │ │ └── NewProject.hs │ └── test │ │ ├── Main.hs │ │ └── fixtures │ │ └── new-project │ │ └── action.yml ├── test │ ├── changelog.md │ ├── readme.md │ ├── test │ │ ├── Ribosome │ │ │ └── Test │ │ │ │ ├── EmbedTmuxTest.hs │ │ │ │ ├── SocketTmuxTest.hs │ │ │ │ └── ReportTest.hs │ │ ├── fixtures │ │ │ └── screenshots │ │ │ │ ├── report-5 │ │ │ │ ├── report-1 │ │ │ │ ├── report-2 │ │ │ │ ├── report-3 │ │ │ │ ├── report-4 │ │ │ │ └── syntax │ │ └── Main.hs │ └── lib │ │ └── Ribosome │ │ ├── Test │ │ ├── Log.hs │ │ ├── Examples │ │ │ └── Example1.hs │ │ ├── Data │ │ │ └── TestConfig.hs │ │ ├── Skip.hs │ │ ├── Ui.hs │ │ └── Error.hs │ │ └── Handler.hs ├── host-test │ ├── changelog.md │ ├── readme.md │ └── lib │ │ └── Ribosome │ │ └── Host │ │ └── Test │ │ └── Data │ │ └── TestConfig.hs ├── integration │ ├── changelog.md │ ├── readme.md │ ├── test │ │ ├── fixtures │ │ │ └── plugin │ │ │ │ ├── result │ │ │ │ ├── packages │ │ │ │ └── test-plugin │ │ │ │ │ ├── app │ │ │ │ │ └── Main.hs │ │ │ │ │ └── test-plugin.cabal │ │ │ │ ├── plugin │ │ │ │ └── run.vim │ │ │ │ └── flake.nix │ │ └── Main.hs │ └── lib │ │ └── Integration.hs ├── host │ ├── changelog.md │ ├── lib │ │ └── Ribosome │ │ │ └── Host │ │ │ ├── TH │ │ │ └── Api │ │ │ │ ├── Sig.hs │ │ │ │ └── Param.hs │ │ │ ├── Data │ │ │ ├── Tuple.hs │ │ │ ├── NvimSocket.hs │ │ │ ├── LuaRef.hs │ │ │ ├── RpcName.hs │ │ │ ├── ChannelId.hs │ │ │ ├── BootError.hs │ │ │ ├── Bar.hs │ │ │ ├── CommandMods.hs │ │ │ ├── CommandRegister.hs │ │ │ ├── RpcBatch.hs │ │ │ ├── StoredReport.hs │ │ │ ├── Response.hs │ │ │ ├── Execution.hs │ │ │ ├── Event.hs │ │ │ ├── Bang.hs │ │ │ ├── HostConfig.hs │ │ │ ├── ApiInfo.hs │ │ │ ├── Request.hs │ │ │ └── RpcError.hs │ │ │ ├── Api │ │ │ ├── Data.hs │ │ │ ├── Event.hs │ │ │ └── Autocmd.hs │ │ │ ├── Path.hs │ │ │ ├── Effect │ │ │ ├── Responses.hs │ │ │ ├── Host.hs │ │ │ ├── Handlers.hs │ │ │ ├── Log.hs │ │ │ ├── UserError.hs │ │ │ └── Reports.hs │ │ │ ├── Interpret.hs │ │ │ ├── Config.hs │ │ │ ├── Interpreter │ │ │ ├── UserError.hs │ │ │ ├── Id.hs │ │ │ ├── Process │ │ │ │ ├── Stdio.hs │ │ │ │ ├── Embed.hs │ │ │ │ └── Socket.hs │ │ │ └── Reports.hs │ │ │ ├── Text.hs │ │ │ ├── Error.hs │ │ │ ├── Remote.hs │ │ │ ├── IOStack.hs │ │ │ ├── Class │ │ │ └── Msgpack │ │ │ │ └── Array.hs │ │ │ ├── Run.hs │ │ │ └── Optparse.hs │ ├── readme.md │ └── test │ │ └── Ribosome │ │ └── Host │ │ └── Test │ │ ├── CommandParamErrorDecls.hs │ │ ├── EventTest.hs │ │ ├── AsyncTest.hs │ │ ├── CommandParamErrorTest.hs │ │ ├── CommandRegisterTest.hs │ │ ├── CommandModsTest.hs │ │ ├── NotifyTest.hs │ │ ├── LogTest.hs │ │ ├── ApiInfoTest.hs │ │ ├── CommandBangTest.hs │ │ └── AutocmdTest.hs ├── menu │ ├── changelog.md │ ├── readme.md │ ├── lib │ │ └── Ribosome │ │ │ └── Menu │ │ │ ├── Data │ │ │ ├── QuitReason.hs │ │ │ ├── MenuQuery.hs │ │ │ ├── CursorLine.hs │ │ │ ├── PromptAction.hs │ │ │ ├── RenderEvent.hs │ │ │ ├── WithCursor.hs │ │ │ ├── CursorIndex.hs │ │ │ ├── MenuStatus.hs │ │ │ ├── MenuResult.hs │ │ │ ├── MenuConfig.hs │ │ │ ├── MenuEvent.hs │ │ │ ├── Menu.hs │ │ │ ├── MenuView.hs │ │ │ ├── State.hs │ │ │ ├── MenuItem.hs │ │ │ ├── WindowConfig.hs │ │ │ ├── RenderMenu.hs │ │ │ ├── InputParams.hs │ │ │ ├── TestMenuConfig.hs │ │ │ ├── NvimMenuState.hs │ │ │ └── MenuAction.hs │ │ │ ├── Settings.hs │ │ │ ├── Prompt │ │ │ └── Data │ │ │ │ ├── PromptInputEvent.hs │ │ │ │ ├── PromptMode.hs │ │ │ │ └── PromptEvent.hs │ │ │ ├── Integral.hs │ │ │ ├── MenuState.hs │ │ │ ├── Effect │ │ │ ├── MenuStream.hs │ │ │ ├── MenuFilter.hs │ │ │ └── MenuUi.hs │ │ │ ├── Test.hs │ │ │ ├── Scratch.hs │ │ │ ├── Prompt.hs │ │ │ ├── Interpreter │ │ │ └── MenuFilter.hs │ │ │ ├── Stream │ │ │ ├── Util.hs │ │ │ └── ParMap.hs │ │ │ ├── Class │ │ │ └── MenuMode.hs │ │ │ └── Lens.hs │ └── test │ │ ├── Ribosome │ │ └── Menu │ │ │ └── Test │ │ │ ├── Util.hs │ │ │ ├── NoMatchTest.hs │ │ │ ├── DeleteCursorTest.hs │ │ │ ├── TriggerPrioTest.hs │ │ │ ├── CursorClampTest.hs │ │ │ └── FilterTest.hs │ │ └── Main.hs └── ribosome │ ├── app │ └── Main.hs │ ├── changelog.md │ ├── lib │ └── Ribosome │ │ ├── Lens.hs │ │ ├── Register.hs │ │ ├── Api │ │ ├── Undo.hs │ │ ├── Process.hs │ │ ├── Sleep.hs │ │ ├── Tabpage.hs │ │ ├── Normal.hs │ │ ├── Tags.hs │ │ ├── Function.hs │ │ ├── Position.hs │ │ ├── Mode.hs │ │ ├── Syntax.hs │ │ ├── Echo.hs │ │ └── Option.hs │ │ ├── Data │ │ ├── CustomConfig.hs │ │ ├── FileBuffer.hs │ │ ├── PluginName.hs │ │ ├── ScratchId.hs │ │ ├── Mode.hs │ │ ├── WindowConfig.hs │ │ ├── Tag.hs │ │ ├── Setting.hs │ │ ├── CliConfig.hs │ │ ├── SyntaxItem.hs │ │ ├── Syntax │ │ │ ├── Syntax.hs │ │ │ ├── Dsl.hs │ │ │ └── SyntaxKind.hs │ │ ├── PersistPathError.hs │ │ ├── WindowView.hs │ │ ├── PersistError.hs │ │ ├── PluginConfig.hs │ │ ├── SettingError.hs │ │ ├── ScratchState.hs │ │ ├── RegisterType.hs │ │ └── Register.hs │ │ ├── Float.hs │ │ ├── Persist.hs │ │ ├── Internal │ │ └── Path.hs │ │ ├── Examples │ │ ├── Example3.hs │ │ ├── Example1.hs │ │ └── Example2.hs │ │ ├── Syntax.hs │ │ ├── Interpreter │ │ ├── PluginName.hs │ │ └── UserError.hs │ │ ├── Text.hs │ │ ├── Scratch.hs │ │ ├── Settings.hs │ │ ├── PluginName.hs │ │ ├── Run.hs │ │ ├── Final.hs │ │ ├── Effect │ │ ├── VariableWatcher.hs │ │ ├── Scratch.hs │ │ ├── PersistPath.hs │ │ └── Settings.hs │ │ ├── Msgpack.hs │ │ ├── Api.hs │ │ ├── IOStack.hs │ │ └── Socket.hs │ ├── test │ ├── Ribosome │ │ └── Test │ │ │ ├── PathTest.hs │ │ │ ├── UndoTest.hs │ │ │ ├── BufferTest.hs │ │ │ ├── PersistTest.hs │ │ │ ├── Error.hs │ │ │ ├── WatcherTest.hs │ │ │ ├── WindowTest.hs │ │ │ └── Wait.hs │ └── Main.hs │ └── readme.md ├── ops ├── version.nix ├── echo.nix ├── template-apps.nix ├── new.nix ├── template-test.nix └── boot.nix ├── cabal.project ├── .gitignore ├── .github └── workflows │ └── push.yml ├── flake.lock └── readme.md /packages/app/changelog.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/test/changelog.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ops/version.nix: -------------------------------------------------------------------------------- 1 | "0.9.9.9" 2 | -------------------------------------------------------------------------------- /packages/host-test/changelog.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/integration/changelog.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cabal.project: -------------------------------------------------------------------------------- 1 | packages: packages/* 2 | -------------------------------------------------------------------------------- /packages/host/changelog.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | -------------------------------------------------------------------------------- /packages/integration/readme.md: -------------------------------------------------------------------------------- 1 | # Ribosome 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist-newstyle/ 2 | /result 3 | /packages/menu/benchmark/fixtures/menu 4 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/TH/Api/Sig.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.TH.Api.Sig where 2 | 3 | -------------------------------------------------------------------------------- /packages/menu/changelog.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | * Add support for multiline menu items. 4 | -------------------------------------------------------------------------------- /packages/integration/test/fixtures/plugin/result: -------------------------------------------------------------------------------- 1 | /nix/store/y5ichmbg3cjfzggxq2bgqm6q2k2x1m24-test-plugin-0 -------------------------------------------------------------------------------- /packages/app/app/Main.hs: -------------------------------------------------------------------------------- 1 | module Main (module Ribosome.App.Cli) where 2 | 3 | import Ribosome.App.Cli (main) 4 | -------------------------------------------------------------------------------- /packages/ribosome/app/Main.hs: -------------------------------------------------------------------------------- 1 | module Main (module Ribosome.App.Cli) where 2 | 3 | import Ribosome.App.Cli (main) 4 | -------------------------------------------------------------------------------- /packages/app/readme.md: -------------------------------------------------------------------------------- 1 | This package is part of [Ribosome](https://hackage.haskell.org/package/ribosome/docs/Ribosome.html). 2 | -------------------------------------------------------------------------------- /packages/host/readme.md: -------------------------------------------------------------------------------- 1 | This package is part of [Ribosome](https://hackage.haskell.org/package/ribosome/docs/Ribosome.html). 2 | -------------------------------------------------------------------------------- /packages/menu/readme.md: -------------------------------------------------------------------------------- 1 | This package is part of [Ribosome](https://hackage.haskell.org/package/ribosome/docs/Ribosome.html). 2 | -------------------------------------------------------------------------------- /packages/host-test/readme.md: -------------------------------------------------------------------------------- 1 | This package is part of [Ribosome](https://hackage.haskell.org/package/ribosome/docs/Ribosome.html). 2 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/Tuple.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Data.Tuple where 2 | 3 | dup :: a -> (a, a) 4 | dup a = 5 | (a, a) 6 | -------------------------------------------------------------------------------- /packages/integration/test/fixtures/plugin/packages/test-plugin/app/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Integration (integrationTest) 4 | 5 | main :: IO () 6 | main = 7 | integrationTest 8 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/QuitReason.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.QuitReason where 2 | 3 | data QuitReason = 4 | Aborted 5 | | 6 | Error Text 7 | deriving stock (Eq, Show) 8 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Api/Data.hs: -------------------------------------------------------------------------------- 1 | {-# options_haddock prune #-} 2 | 3 | module Ribosome.Host.Api.Data where 4 | 5 | import Ribosome.Host.TH.Api.GenerateData (generateData) 6 | 7 | generateData 8 | -------------------------------------------------------------------------------- /packages/ribosome/changelog.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | * When the argument passed to an `ArgList` handler is a list with no following arguments, accept those as if the 4 | elements were passed as individual arguments. 5 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/NvimSocket.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Data.NvimSocket where 2 | 3 | import Path (Abs, File, Path) 4 | 5 | newtype NvimSocket = 6 | NvimSocket { unNvimSocket :: Path Abs File } 7 | deriving stock (Eq, Show) 8 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/MenuQuery.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.MenuQuery where 2 | 3 | newtype MenuQuery = 4 | MenuQuery { unMenuQuery :: Text } 5 | deriving stock (Eq, Show) 6 | deriving newtype (Semigroup, Monoid, Ord, IsString) 7 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/CursorLine.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.CursorLine where 2 | 3 | newtype CursorLine = 4 | CursorLine { unCursorLine :: Word } 5 | deriving stock (Eq, Show, Generic) 6 | deriving newtype (Num, Real, Enum, Integral, Ord) 7 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Settings.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Settings where 2 | 3 | import Ribosome.Data.Setting (Setting (Setting)) 4 | 5 | menuCloseFloats :: Setting Bool 6 | menuCloseFloats = 7 | Setting "ribosome_menu_close_floats" False (Just True) 8 | -------------------------------------------------------------------------------- /ops/echo.nix: -------------------------------------------------------------------------------- 1 | '' 2 | mag="\e[35m" 3 | green="\e[32m" 4 | msg="\e[34m" 5 | emph() { 6 | echo -e "\e[33m$*$msg" 7 | } 8 | log() { 9 | echo -e "\e[1m$green>>=\e[0m \e[34m$*\e[0m" >&2 10 | } 11 | die() { 12 | log "\e[31mERROR: $*" 13 | exit 1 14 | } 15 | '' 16 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Path.hs: -------------------------------------------------------------------------------- 1 | -- |Path combinators. 2 | module Ribosome.Host.Path where 3 | 4 | import Path (Path, toFilePath) 5 | 6 | -- |Render a 'Path' as 'Text'. 7 | pathText :: 8 | Path b t -> 9 | Text 10 | pathText = 11 | toText . toFilePath 12 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Effect/Responses.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Effect.Responses where 2 | 3 | data Responses k v :: Effect where 4 | Add :: Responses k v m k 5 | Wait :: k -> Responses k v m v 6 | Respond :: k -> v -> Responses k v m () 7 | 8 | makeSem ''Responses 9 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Prompt/Data/PromptInputEvent.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Prompt.Data.PromptInputEvent where 2 | 3 | data PromptInputEvent = 4 | Init 5 | | 6 | Character Text 7 | | 8 | Interrupt 9 | | 10 | Error Text 11 | deriving stock (Eq, Show) 12 | -------------------------------------------------------------------------------- /packages/app/lib/Ribosome/App/Data/TemplateTree.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.App.Data.TemplateTree where 2 | 3 | import Path (Dir, File, Path, Rel) 4 | 5 | data TemplateTree = 6 | TDir (Path Rel Dir) [TemplateTree] 7 | | 8 | TFile (Path Rel File) Text 9 | deriving stock (Eq, Show) 10 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/TH/Api/Param.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.TH.Api.Param where 2 | 3 | import Language.Haskell.TH (Name, Type) 4 | import Prelude hiding (Type) 5 | 6 | data Param = 7 | Param { paramName :: Name, monoType :: Type, polyName :: Maybe Name } 8 | deriving stock (Eq, Show) 9 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/PromptAction.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.PromptAction where 2 | 3 | import Ribosome.Menu.Prompt.Data.Prompt (PromptState) 4 | 5 | data PromptAction a = 6 | Continue 7 | | 8 | Quit a 9 | | 10 | Update PromptState 11 | deriving stock (Eq, Show) 12 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Lens.hs: -------------------------------------------------------------------------------- 1 | {-# options_haddock prune, hide #-} 2 | 3 | -- |Lens combinators, used internally 4 | module Ribosome.Lens where 5 | 6 | (<|>~) :: Alternative f => ASetter s t (f a) (f a) -> f a -> s -> t 7 | l <|>~ fa = 8 | over l (<|> fa) 9 | {-# inline (<|>~) #-} 10 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/RenderEvent.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.RenderEvent where 2 | 3 | import Ribosome.Menu.Data.MenuAction (RenderAnchor) 4 | 5 | data RenderEvent = 6 | RenderEvent { 7 | desc :: Text, 8 | anchor :: RenderAnchor 9 | } 10 | deriving stock (Eq, Show) 11 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Integral.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Integral where 2 | 3 | subClamp :: 4 | ∀ a b . 5 | Ord b => 6 | Num b => 7 | Integral a => 8 | b -> 9 | a -> 10 | b 11 | subClamp a (fromIntegral -> b) | b < a = a - b 12 | | otherwise = 0 13 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/WithCursor.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.WithCursor where 2 | 3 | import Ribosome.Menu.Data.CursorIndex (CursorIndex) 4 | 5 | data WithCursor s = 6 | WithCursor { 7 | state :: s, 8 | cursor :: CursorIndex 9 | } 10 | deriving stock (Eq, Show, Generic) 11 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Register.hs: -------------------------------------------------------------------------------- 1 | -- |API for registers. 2 | module Ribosome.Register ( 3 | module Ribosome.Data.Register, 4 | module Ribosome.Data.RegisterType, 5 | ) where 6 | 7 | import Ribosome.Data.Register (Register (..), registerRepr) 8 | import Ribosome.Data.RegisterType (RegisterType (..)) 9 | -------------------------------------------------------------------------------- /packages/integration/test/fixtures/plugin/plugin/run.vim: -------------------------------------------------------------------------------- 1 | let s:repo = fnamemodify(expand(''), ":p:h:h") 2 | let r = jobstart( 3 | \ ['nix', 'run', '--update-input', 'ribosome', '.#test-plugin'], 4 | \ { 'rpc': v:true, 'cwd': s:repo, 5 | \ 'on_stderr': { j, d, e -> chansend(2, d) } } 6 | \ ) 7 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Api/Undo.hs: -------------------------------------------------------------------------------- 1 | -- |API functions for @:undo@. 2 | module Ribosome.Api.Undo where 3 | 4 | import Ribosome.Host.Api.Data (nvimCommand) 5 | import Ribosome.Host.Effect.Rpc (Rpc) 6 | 7 | -- |Run @:undo@. 8 | undo :: 9 | Member Rpc r => 10 | Sem r () 11 | undo = 12 | nvimCommand "undo" 13 | -------------------------------------------------------------------------------- /ops/template-apps.nix: -------------------------------------------------------------------------------- 1 | { self, config, lib }: { 2 | 3 | template-test = { 4 | type = "app"; 5 | program = "${import ./template-test.nix { inherit self config lib; }}"; 6 | }; 7 | 8 | new = { 9 | type = "app"; 10 | program = "${import ./new.nix { inherit self config lib; }}"; 11 | }; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/CursorIndex.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.CursorIndex where 2 | 3 | newtype CursorIndex = 4 | CursorIndex { unCursorIndex :: Word } 5 | deriving stock (Eq, Show, Generic) 6 | deriving newtype (Num, Real, Enum, Integral, Ord) 7 | 8 | instance Default CursorIndex where 9 | def = 0 10 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/MenuState.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.MenuState ( 2 | module Ribosome.Menu.Class.MenuMode, 3 | module Ribosome.Menu.Class.MenuState, 4 | ) where 5 | 6 | import Ribosome.Menu.Class.MenuMode (MenuMode (..)) 7 | import Ribosome.Menu.Class.MenuState (MenuState (..), entries, entryCount, itemCount, items, query) 8 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/CustomConfig.hs: -------------------------------------------------------------------------------- 1 | -- |Disambiguation type for @Reader@. 2 | module Ribosome.Data.CustomConfig where 3 | 4 | -- |Disambiguation type used for the custom CLI configuration that is polymorphic in the stack. 5 | newtype CustomConfig c = 6 | CustomConfig { unCustomConfig :: c } 7 | deriving stock (Eq, Show) 8 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Effect/Host.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Effect.Host where 2 | 3 | import Ribosome.Host.Data.Request (Request) 4 | import Ribosome.Host.Data.Response (Response) 5 | 6 | data Host :: Effect where 7 | Request :: Request -> Host m Response 8 | Notification :: Request -> Host m () 9 | 10 | makeSem ''Host 11 | -------------------------------------------------------------------------------- /packages/test/readme.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This package contains test utilities for [Ribosome](https://hackage.haskell.org/package/ribosome/docs/Ribosome.html), 4 | a Neovim plugin host and framework for Haskell. 5 | 6 | Please consult [Hackage](https://hackage.haskell.org/package/ribosome-test/docs/Ribosome-Test.html) for the 7 | documentation. 8 | -------------------------------------------------------------------------------- /packages/app/lib/Ribosome/App/Templates/MainHs.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.App.Templates.MainHs where 2 | 3 | import Exon (exon) 4 | 5 | import Ribosome.App.Data (ModuleName (ModuleName)) 6 | 7 | mainHs :: ModuleName -> Text 8 | mainHs (ModuleName modName) = 9 | [exon|module Main (main) where 10 | 11 | import #{modName}.Plugin (main) 12 | |] 13 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/MenuStatus.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.MenuStatus where 2 | 3 | data MenuStatus = 4 | MenuStatus { 5 | filter :: Text, 6 | middle :: Int -> Maybe Text, 7 | bottom :: Int -> [Text], 8 | itemCount :: Word, 9 | entryCount :: Word, 10 | cursor :: Word 11 | } 12 | deriving stock (Generic) 13 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Effect/Handlers.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Effect.Handlers where 2 | 3 | import Data.MessagePack (Object) 4 | 5 | import Ribosome.Host.Data.Request (RpcMethod) 6 | 7 | data Handlers :: Effect where 8 | Register :: Handlers m () 9 | Run :: RpcMethod -> [Object] -> Handlers m (Maybe Object) 10 | 11 | makeSem ''Handlers 12 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/LuaRef.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Data.LuaRef where 2 | 3 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode) 4 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode) 5 | 6 | newtype LuaRef = 7 | LuaRef { unLuaRef :: Int64 } 8 | deriving stock (Generic) 9 | deriving anyclass (MsgpackDecode, MsgpackEncode) 10 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Api/Process.hs: -------------------------------------------------------------------------------- 1 | -- |API functions for process IDs. 2 | module Ribosome.Api.Process where 3 | 4 | import Ribosome.Host.Api.Data (vimCallFunction) 5 | import Ribosome.Host.Effect.Rpc (Rpc) 6 | 7 | -- |Return Neovim's process ID. 8 | vimPid :: 9 | Member Rpc r => 10 | Sem r Int 11 | vimPid = 12 | vimCallFunction "getpid" [] 13 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Float.hs: -------------------------------------------------------------------------------- 1 | -- |Data types for floating window configuration. 2 | module Ribosome.Float ( 3 | module Ribosome.Data.FloatOptions 4 | ) where 5 | 6 | import Ribosome.Data.FloatOptions ( 7 | FloatAnchor (..), 8 | FloatBorder (..), 9 | FloatOptions (..), 10 | FloatRelative (..), 11 | FloatStyle (..), 12 | FloatZindex (..), 13 | ) 14 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/RpcName.hs: -------------------------------------------------------------------------------- 1 | -- |The name of an RPC handler 2 | module Ribosome.Host.Data.RpcName where 3 | 4 | -- |This name is used for the function or command registered in Neovim as well as to internally identify a handler. 5 | newtype RpcName = 6 | RpcName { unRpcName :: Text } 7 | deriving stock (Eq, Show, Generic) 8 | deriving newtype (IsString, Ord) 9 | -------------------------------------------------------------------------------- /packages/integration/test/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Integration.Test.PluginTest (test_plugin) 4 | import Polysemy.Test (unitTest) 5 | import Test.Tasty (TestTree, defaultMain, testGroup) 6 | 7 | tests :: TestTree 8 | tests = 9 | testGroup "integration" [ 10 | unitTest "plugin" test_plugin 11 | ] 12 | 13 | main :: IO () 14 | main = 15 | defaultMain tests 16 | -------------------------------------------------------------------------------- /packages/app/test/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Polysemy.Test (unitTest) 4 | import Ribosome.App.Test.NewProjectTest (test_newProject) 5 | import Test.Tasty (TestTree, defaultMain, testGroup) 6 | 7 | tests :: TestTree 8 | tests = 9 | testGroup "ribosome" [ 10 | unitTest "create a new project" test_newProject 11 | ] 12 | 13 | main :: IO () 14 | main = 15 | defaultMain tests 16 | -------------------------------------------------------------------------------- /packages/integration/test/fixtures/plugin/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Test plugin"; 3 | 4 | inputs.ribosome.url = "path:RIBOSOME"; 5 | 6 | outputs = { ribosome, ... }: 7 | ribosome.inputs.hix.lib.pro { 8 | depsFull = [ribosome]; 9 | envs.dev.localPackage = api: api.fast; 10 | packages.test-plugin = { src = ./packages/test-plugin; }; 11 | ifd = true; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Prompt/Data/PromptMode.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Prompt.Data.PromptMode where 2 | 3 | import Ribosome.Data.Mapping (MapMode (MapInsert, MapNormal)) 4 | 5 | data PromptMode = 6 | Insert 7 | | 8 | Normal 9 | deriving stock (Eq, Show, Ord) 10 | 11 | toMapMode :: PromptMode -> MapMode 12 | toMapMode = \case 13 | Insert -> MapInsert 14 | Normal -> MapNormal 15 | -------------------------------------------------------------------------------- /packages/test/test/Ribosome/Test/EmbedTmuxTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Test.EmbedTmuxTest where 2 | 3 | import Polysemy.Test (UnitTest, assert) 4 | 5 | import Ribosome.Host.Api.Data (nvimGetVar, nvimSetVar) 6 | import Ribosome.Test.EmbedTmux (testEmbedTmux) 7 | 8 | test_embedTmux :: UnitTest 9 | test_embedTmux = 10 | testEmbedTmux do 11 | nvimSetVar "test" True 12 | assert =<< nvimGetVar "test" 13 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Effect/Log.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Effect.Log where 2 | 3 | type StderrLog = 4 | Tagged "stderr" Log 5 | 6 | type FileLog = 7 | Tagged "file" Log 8 | 9 | stderrLog :: 10 | Member StderrLog r => 11 | InterpreterFor Log r 12 | stderrLog = 13 | tag @"stderr" 14 | 15 | fileLog :: 16 | Member FileLog r => 17 | InterpreterFor Log r 18 | fileLog = 19 | tag @"file" 20 | -------------------------------------------------------------------------------- /packages/test/test/Ribosome/Test/SocketTmuxTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Test.SocketTmuxTest where 2 | 3 | import Polysemy.Test (UnitTest, assert) 4 | 5 | import Ribosome.Host.Api.Data (nvimGetVar, nvimSetVar) 6 | import Ribosome.Test.SocketTmux (testSocketTmux) 7 | 8 | test_socketTmux :: UnitTest 9 | test_socketTmux = 10 | testSocketTmux do 11 | nvimSetVar "test" True 12 | assert =<< nvimGetVar "test" 13 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/MenuResult.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.MenuResult where 2 | 3 | import Exon (exon) 4 | 5 | data MenuResult a = 6 | Success a 7 | | 8 | Aborted 9 | | 10 | Error Text 11 | deriving stock (Eq, Show, Functor) 12 | 13 | describe :: MenuResult a -> Text 14 | describe = \case 15 | Success _ -> "Success" 16 | Aborted -> "Aborted" 17 | Error e -> [exon|Error #{e}|] 18 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Effect/MenuStream.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Effect.MenuStream where 2 | 3 | import Streamly.Prelude (SerialT) 4 | 5 | data MenuStream :: Effect where 6 | MenuStream :: 7 | SerialT IO i -> 8 | m (Maybe query) -> 9 | (query -> m render) -> 10 | ([i] -> m render) -> 11 | (render -> m ()) -> 12 | m () -> 13 | MenuStream m () 14 | 15 | makeSem ''MenuStream 16 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Interpret.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Interpret where 2 | 3 | type (|>) :: [k] -> k -> [k] 4 | type (a :: [k]) |> (b :: k) = 5 | a ++ '[b] 6 | 7 | infixl 6 |> 8 | 9 | type HigherOrder r r' = 10 | Members r' (r ++ r') 11 | 12 | with :: Sem r a -> (a -> InterpreterFor eff r) -> InterpreterFor eff r 13 | with acquire f sem = do 14 | a <- acquire 15 | f a sem 16 | {-# inline with #-} 17 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/ChannelId.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Data.ChannelId where 2 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode (fromMsgpack)) 3 | 4 | newtype ChannelId = 5 | ChannelId { unChannelId :: Int64 } 6 | deriving stock (Eq, Show) 7 | deriving newtype (Num, Real, Enum, Integral, Ord) 8 | 9 | instance MsgpackDecode ChannelId where 10 | fromMsgpack = 11 | fmap ChannelId . fromMsgpack 12 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/MenuConfig.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.MenuConfig where 2 | 3 | data MenuConfig = 4 | MenuConfig { 5 | sync :: Bool 6 | } 7 | deriving stock (Eq, Show, Generic) 8 | 9 | instance Default MenuConfig where 10 | def = 11 | MenuConfig False 12 | 13 | menuSync :: 14 | Member (Reader MenuConfig) r => 15 | Sem r a -> 16 | Sem r a 17 | menuSync = 18 | local (#sync .~ True) 19 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Config.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Config where 2 | 3 | import qualified Ribosome.Host.Data.HostConfig as HostConfig 4 | import Ribosome.Host.Data.HostConfig (HostConfig (HostConfig), LogConfig) 5 | 6 | interpretLogConfig :: 7 | Member (Reader HostConfig) r => 8 | InterpreterFor (Reader LogConfig) r 9 | interpretLogConfig sem = 10 | ask >>= \ HostConfig {hostLog} -> 11 | runReader hostLog sem 12 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Persist.hs: -------------------------------------------------------------------------------- 1 | -- |API for the effect 'Persist'. 2 | module Ribosome.Persist ( 3 | module Ribosome.Effect.Persist, 4 | module Ribosome.Effect.PersistPath, 5 | module Ribosome.Data.PersistError, 6 | module Ribosome.Data.PersistPathError, 7 | ) where 8 | 9 | import Ribosome.Data.PersistError 10 | import Ribosome.Data.PersistPathError 11 | import Ribosome.Effect.Persist 12 | import Ribosome.Effect.PersistPath 13 | -------------------------------------------------------------------------------- /packages/host-test/lib/Ribosome/Host/Test/Data/TestConfig.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Test.Data.TestConfig where 2 | 3 | import Ribosome.Host.Data.HostConfig (HostConfig (HostConfig), dataLogConc) 4 | 5 | data TestConfig = 6 | TestConfig { 7 | freezeTime :: Bool, 8 | host :: HostConfig 9 | } 10 | deriving stock (Eq, Show, Generic) 11 | 12 | instance Default TestConfig where 13 | def = 14 | TestConfig False (HostConfig def { dataLogConc = False }) 15 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Interpreter/UserError.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Interpreter.UserError where 2 | 3 | import Polysemy.Log (Severity (Info)) 4 | 5 | import Ribosome.Host.Effect.UserError (UserError (UserError)) 6 | 7 | interpretUserErrorInfo :: InterpreterFor UserError r 8 | interpretUserErrorInfo = 9 | interpret \case 10 | UserError e severity | severity >= Info -> 11 | pure (Just [e]) 12 | UserError _ _ -> 13 | pure Nothing 14 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Test.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Test ( 2 | module Ribosome.Menu.Test.Run, 3 | module Ribosome.Menu.Test.Main, 4 | module Ribosome.Menu.Data.TestMenuConfig, 5 | module Ribosome.Menu.Test.Loop, 6 | module Ribosome.Menu.Prompt.Run, 7 | ) where 8 | 9 | import Ribosome.Menu.Data.TestMenuConfig 10 | import Ribosome.Menu.Prompt.Run 11 | import Ribosome.Menu.Test.Loop 12 | import Ribosome.Menu.Test.Main 13 | import Ribosome.Menu.Test.Run 14 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/FileBuffer.hs: -------------------------------------------------------------------------------- 1 | -- |Data type representing a buffer associated with a file system path. 2 | module Ribosome.Data.FileBuffer where 3 | 4 | import Path (Abs, File, Path) 5 | 6 | import Ribosome.Host.Api.Data (Buffer) 7 | 8 | -- |Data type representing a buffer associated with a file system path. 9 | data FileBuffer = 10 | FileBuffer { 11 | buffer :: Buffer, 12 | path :: Path Abs File 13 | } 14 | deriving stock (Eq, Show, Generic) 15 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Internal/Path.hs: -------------------------------------------------------------------------------- 1 | {-# options_haddock prune, hide #-} 2 | 3 | -- |Internal combinators for paths. 4 | module Ribosome.Internal.Path where 5 | 6 | import Exon (exon) 7 | 8 | import Ribosome.Host.Data.Report (Report) 9 | 10 | failInvalidPath :: 11 | Member (Stop Report) r => 12 | Text -> 13 | Maybe a -> 14 | Sem r a 15 | failInvalidPath spec result = 16 | withFrozenCallStack do 17 | stopNote (fromText [exon|Invalid path: #{spec}|]) result 18 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Interpreter/Id.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Interpreter.Id where 2 | 3 | import Conc (interpretAtomic) 4 | import Polysemy.Input (Input (Input)) 5 | 6 | -- |Interpret 'Input' by incrementing a numeric type starting from @1@. 7 | interpretInputNum :: 8 | ∀ a r . 9 | Num a => 10 | Member (Embed IO) r => 11 | InterpreterFor (Input a) r 12 | interpretInputNum = 13 | interpretAtomic @a 1 . 14 | reinterpret \ Input -> 15 | atomicState' \ i -> (i + 1, i) 16 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/MenuEvent.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.MenuEvent where 2 | 3 | import Ribosome.Menu.Prompt.Data.Prompt (Prompt) 4 | 5 | data QueryEvent = 6 | Refined 7 | | 8 | Reset 9 | | 10 | Modal 11 | deriving stock (Eq, Show, Ord) 12 | 13 | data MenuEvent = 14 | Query QueryEvent 15 | | 16 | Inserted 17 | | 18 | PromptUpdated Prompt 19 | | 20 | PromptLoop 21 | | 22 | Rendered 23 | | 24 | Exhausted 25 | deriving stock (Eq, Show, Ord) 26 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/PluginName.hs: -------------------------------------------------------------------------------- 1 | -- |Data type 'PluginName' 2 | module Ribosome.Data.PluginName where 3 | 4 | -- |Represents the name of the plugin, to be used via 'Reader' by all its components. 5 | -- 6 | -- The name is usually provided by main function combinators like 'Ribosome.runNvimPluginIO' via @'Reader' 7 | -- 'Ribosome.PluginConfig'@. 8 | newtype PluginName = 9 | PluginName { unPluginName :: Text } 10 | deriving stock (Eq, Show) 11 | deriving newtype (IsString, Ord) 12 | -------------------------------------------------------------------------------- /packages/menu/test/Ribosome/Menu/Test/Util.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Test.Util where 2 | 3 | import qualified Streamly.Prelude as Stream 4 | import Streamly.Prelude (SerialT) 5 | 6 | import Ribosome.Menu.Data.MenuItem (MenuItem, simpleMenuItem) 7 | 8 | staticMenuItems :: 9 | [Text] -> 10 | [MenuItem Text] 11 | staticMenuItems = 12 | fmap \ t -> simpleMenuItem t t 13 | 14 | mkItems :: 15 | [Text] -> 16 | SerialT IO (MenuItem Text) 17 | mkItems = 18 | Stream.fromList . fmap (simpleMenuItem "name") 19 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/ScratchId.hs: -------------------------------------------------------------------------------- 1 | -- |The ID type used to store active scratch buffers. 2 | module Ribosome.Data.ScratchId where 3 | 4 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode) 5 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode) 6 | 7 | -- |The ID type used to store active scratch buffers. 8 | newtype ScratchId = 9 | ScratchId { unScratchId :: Text } 10 | deriving stock (Eq, Show, Generic) 11 | deriving newtype (IsString, Ord, MsgpackDecode, MsgpackEncode) 12 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Text.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Text where 2 | 3 | import qualified Data.Text as Text 4 | import Exon (exon) 5 | import Text.Casing (pascal) 6 | 7 | ellipsize :: Int -> Text -> Text 8 | ellipsize maxChars msg = 9 | [exon|#{pre}#{if Text.null post then "" else "..."}|] 10 | where 11 | (pre, post) = 12 | Text.splitAt maxChars msg 13 | 14 | pascalCase :: 15 | ToString a => 16 | IsString b => 17 | a -> 18 | b 19 | pascalCase = 20 | fromString . pascal . toString 21 | -------------------------------------------------------------------------------- /packages/test/lib/Ribosome/Test/Log.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Test.Log where 2 | 3 | import Log (Severity) 4 | 5 | import Ribosome.Test.Data.TestConfig (TestConfig) 6 | 7 | testLogLevelConf :: 8 | Severity -> 9 | (TestConfig -> a) -> 10 | TestConfig -> 11 | a 12 | testLogLevelConf level f conf = 13 | f (conf & #plugin . #host . #hostLog . #logLevelStderr .~ level) 14 | 15 | testLogLevel :: 16 | Severity -> 17 | (TestConfig -> a) -> 18 | a 19 | testLogLevel level f = 20 | testLogLevelConf level f def 21 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/Mode.hs: -------------------------------------------------------------------------------- 1 | -- |Codec data type for the result type of @nvim_get_mode@. 2 | module Ribosome.Data.Mode where 3 | 4 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode) 5 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode) 6 | 7 | -- |Codec data type for the result type of @nvim_get_mode@. 8 | data NvimMode = 9 | NvimMode { 10 | mode :: Text, 11 | blocking :: Bool 12 | } 13 | deriving stock (Eq, Show, Generic) 14 | deriving anyclass (MsgpackEncode, MsgpackDecode) 15 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Examples/Example3.hs: -------------------------------------------------------------------------------- 1 | {-# options_haddock prune, hide #-} 2 | 3 | -- |An example for the docs. 4 | module Ribosome.Examples.Example3 where 5 | 6 | import qualified Data.Text.IO as Text 7 | 8 | import Ribosome 9 | import Ribosome.Api 10 | 11 | ping :: Handler r Text 12 | ping = 13 | pure "Ping" 14 | 15 | main :: IO () 16 | main = 17 | runEmbedPluginIO_ "ping-plugin" [rpcFunction "Ping" Sync ping] do 18 | ignoreRpcError do 19 | embed . Text.putStrLn =<< nvimCallFunction "Ping" [] 20 | -------------------------------------------------------------------------------- /packages/ribosome/test/Ribosome/Test/PathTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Test.PathTest where 2 | 3 | import Path (reldir) 4 | import qualified Polysemy.Test as Test 5 | import Polysemy.Test (UnitTest, assertEq) 6 | 7 | import Ribosome.Api.Path (nvimDir, nvimSetCwd) 8 | import Ribosome.Host.Test.Run (embedTest_) 9 | import Ribosome.Test.Error (testHandler) 10 | 11 | test_nvimPath :: UnitTest 12 | test_nvimPath = 13 | embedTest_ $ testHandler do 14 | dir <- Test.tempDir [reldir|path|] 15 | nvimSetCwd dir 16 | assertEq dir =<< nvimDir "" 17 | -------------------------------------------------------------------------------- /ops/new.nix: -------------------------------------------------------------------------------- 1 | { self, config, lib }: 2 | let 3 | 4 | nix = args: '' 5 | ${config.pkgs.nix}/bin/nix --option extra-substituters 'https://tek.cachix.org' \ 6 | --option extra-trusted-public-keys 'tek.cachix.org-1:+sdc73WFq8aEKnrVv5j/kuhmnW2hQJuqdPJF5SnaCBk=' ${args} 7 | ''; 8 | 9 | script = config.pkgs.writeScript "ribosome-new" '' 10 | #!${config.pkgs.bash}/bin/bash 11 | set -e 12 | dir=$(${nix "run path:${self}#ribosome -- new --print-dir $*"}) 13 | cd $dir 14 | ${nix "run .#gen"} 15 | ''; 16 | 17 | in script 18 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Examples/Example1.hs: -------------------------------------------------------------------------------- 1 | {-# options_haddock prune, hide #-} 2 | 3 | -- |An example for the docs. 4 | module Ribosome.Examples.Example1 where 5 | 6 | import Ribosome 7 | import Ribosome.Api 8 | 9 | count :: 10 | Member (Rpc !! RpcError) r => 11 | Int -> 12 | Handler r Int 13 | count n = do 14 | s <- 0 PromptEvent 18 | updateInsert p = 19 | Update (p & #mode .~ Insert) 20 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Examples/Example2.hs: -------------------------------------------------------------------------------- 1 | {-# options_haddock prune, hide #-} 2 | 3 | -- |An example for the docs. 4 | module Ribosome.Examples.Example2 where 5 | 6 | import Data.MessagePack (Object) 7 | 8 | import Ribosome 9 | import Ribosome.Api 10 | 11 | changed :: 12 | Members NvimPlugin r => 13 | Object -> 14 | Handler r () 15 | changed value = 16 | ignoreRpcError (echo ("Update value to: " <> show value)) 17 | 18 | main :: IO () 19 | main = 20 | runRemoteStack "watch-plugin" (watchVariables [("trigger", changed)] remotePlugin) 21 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/BootError.hs: -------------------------------------------------------------------------------- 1 | -- |The fatal error type 2 | module Ribosome.Host.Data.BootError where 3 | 4 | -- |This type represents the singular fatal error used by Ribosome. 5 | -- 6 | -- Contrary to all other errors, this one is used with 'Error' instead of 'Stop'. 7 | -- It is only thrown from intialization code of interpreters when operation of the plugin is impossible due to the error 8 | -- condition. 9 | newtype BootError = 10 | BootError { unBootError :: Text } 11 | deriving stock (Eq, Show) 12 | deriving newtype (IsString, Ord) 13 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/Tag.hs: -------------------------------------------------------------------------------- 1 | -- |Data type for tags. 2 | module Ribosome.Data.Tag where 3 | 4 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode) 5 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode) 6 | 7 | -- |The return type of 'Ribosome.Api.taglist', representing a ctags-like entry of a tags file. 8 | data Tag = 9 | Tag { 10 | name :: Text, 11 | filename :: Text, 12 | kind :: Text, 13 | static :: Bool, 14 | cmd :: Text 15 | } 16 | deriving stock (Eq, Show, Generic) 17 | deriving anyclass (MsgpackEncode, MsgpackDecode) 18 | -------------------------------------------------------------------------------- /packages/app/lib/Ribosome/App/ProjectPath.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.App.ProjectPath where 2 | 3 | import Path (Abs, Dir, Path, Rel, ()) 4 | import Path.IO (getCurrentDir) 5 | 6 | import Ribosome.App.Error (RainbowError, ioError) 7 | 8 | cwdProjectPath :: 9 | Members [Stop RainbowError, Embed IO] r => 10 | Bool -> 11 | Path Rel Dir -> 12 | Sem r (Path Abs Dir) 13 | cwdProjectPath append name = do 14 | cwd <- stopTryIOError err getCurrentDir 15 | pure (if append then cwd name else cwd) 16 | where 17 | err = 18 | ioError ["Could not determine current directory"] 19 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/Bar.hs: -------------------------------------------------------------------------------- 1 | -- |Special command parameter that enables command chaining. 2 | module Ribosome.Host.Data.Bar where 3 | 4 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode (fromMsgpack)) 5 | 6 | -- |When this type is used as a parameter of a command handler function, the command is declared with the @-bar@ option, 7 | -- allowing other commands to be chained after it with @|@. 8 | -- 9 | -- This has no effect on the execution. 10 | data Bar = 11 | Bar 12 | deriving stock (Eq, Show) 13 | 14 | instance MsgpackDecode Bar where 15 | fromMsgpack _ = 16 | Right Bar 17 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Scratch.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Scratch where 2 | 3 | import Ribosome.Data.ScratchOptions (ScratchOptions, scratch) 4 | import Ribosome.Lens ((<|>~)) 5 | import Ribosome.Data.ScratchId (ScratchId) 6 | 7 | menuItemsScratchId :: ScratchId 8 | menuItemsScratchId = "ribosome-menu-items" 9 | 10 | menuScratch :: ScratchOptions 11 | menuScratch = scratch menuItemsScratchId 12 | 13 | menuScratchSized :: Int -> ScratchOptions 14 | menuScratchSized n = 15 | menuScratch & #size ?~ n 16 | 17 | ensureSize :: Int -> ScratchOptions -> ScratchOptions 18 | ensureSize n = 19 | #size <|>~ Just n 20 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/Setting.hs: -------------------------------------------------------------------------------- 1 | -- |Data type abstracting a Neovim variable with a default value. 2 | module Ribosome.Data.Setting where 3 | 4 | -- |This type is used by the effect 'Ribosome.Settings', representing a Neovim variable associated with a plugin. 5 | -- 6 | -- It has a name, can optionally prefixed by the plugin's name and may define a default value that is used when the 7 | -- variable is undefined. 8 | -- 9 | -- The type parameter determines how the Neovim value is decoded. 10 | data Setting a = 11 | Setting { 12 | key :: Text, 13 | prefix :: Bool, 14 | fallback :: Maybe a 15 | } 16 | -------------------------------------------------------------------------------- /packages/app/lib/Ribosome/App/Templates/TestMainHs.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.App.Templates.TestMainHs where 2 | 3 | import Exon (exon) 4 | 5 | import Ribosome.App.Data (ModuleName (ModuleName)) 6 | 7 | testMainHs :: ModuleName -> Text 8 | testMainHs (ModuleName modName) = 9 | [exon|module Main where 10 | 11 | import #{modName}.Test.PingTest (test_ping) 12 | import Polysemy.Test (unitTest) 13 | import Test.Tasty (TestTree, defaultMain, testGroup) 14 | 15 | tests :: TestTree 16 | tests = 17 | testGroup "all" [ 18 | unitTest "ping" test_ping 19 | ] 20 | 21 | 22 | main :: IO () 23 | main = 24 | defaultMain tests 25 | |] 26 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Prompt.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Prompt ( 2 | module Ribosome.Menu.Prompt.Data.Prompt, 3 | module Ribosome.Menu.Prompt.Data.PromptMode, 4 | module Ribosome.Menu.Prompt.Data.PromptEvent, 5 | menuPrompt, 6 | menuPromptState, 7 | ) where 8 | 9 | import Ribosome.Menu.Action (MenuSem) 10 | import Ribosome.Menu.Prompt.Data.Prompt 11 | import Ribosome.Menu.Prompt.Data.PromptEvent 12 | import Ribosome.Menu.Prompt.Data.PromptMode 13 | 14 | menuPromptState :: 15 | MenuSem s r PromptState 16 | menuPromptState = ask 17 | 18 | menuPrompt :: 19 | MenuSem s r Prompt 20 | menuPrompt = asks (.prompt) 21 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/CommandMods.hs: -------------------------------------------------------------------------------- 1 | -- |Special command parameter that exposes the used modifiers. 2 | module Ribosome.Host.Data.CommandMods where 3 | 4 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode) 5 | 6 | -- |When this type is used as a parameter of a command handler function, the RPC trigger uses the special token 7 | -- @@ in the call. 8 | -- 9 | -- This type then contains the list of pre-command modifiers specified by the user, like @:belowright@. 10 | newtype CommandMods = 11 | CommandMods { unCommandMods :: Text } 12 | deriving stock (Eq, Show) 13 | deriving newtype (MsgpackDecode) 14 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/CommandRegister.hs: -------------------------------------------------------------------------------- 1 | -- |Special command parameter that exposes the used register. 2 | module Ribosome.Host.Data.CommandRegister where 3 | 4 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode) 5 | 6 | -- |When this type is used as a parameter of a command handler function, the RPC trigger uses the special token 7 | -- @@ in the call. 8 | -- 9 | -- This type then contains the name of the register specified by the user. 10 | newtype CommandRegister = 11 | CommandRegister { unCommandRegister :: Text } 12 | deriving stock (Eq, Show) 13 | deriving newtype (IsString, Ord, MsgpackDecode) 14 | -------------------------------------------------------------------------------- /ops/template-test.nix: -------------------------------------------------------------------------------- 1 | { self, config, lib }: 2 | let 3 | 4 | script = config.pkgs.writeScript "ribosome-template-test" '' 5 | #!${config.pkgs.zsh}/bin/zsh 6 | setopt err_exit no_unset 7 | name='ribo-tpl' 8 | dir=$(mktemp --directory) 9 | cd $dir 10 | nix run path:${self}#new -- $name -a Author -m maint@ain.er -o org -r proj -b main --skip-cachix --flake-url 'path:${self}' 11 | cd $dir/$name 12 | nix build .#ribo-tpl.static 13 | if [[ ! -e result/bin/$name ]] 14 | then 15 | print "result/bin/$name not generated" 16 | exit 1 17 | fi 18 | print 'success' 19 | ''; 20 | 21 | in script 22 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Api/Sleep.hs: -------------------------------------------------------------------------------- 1 | -- |API functions for sleeping in Neovim. 2 | module Ribosome.Api.Sleep where 3 | 4 | import Exon (exon) 5 | 6 | import Ribosome.Host.Api.Data (nvimCommand) 7 | import Ribosome.Host.Effect.Rpc (Rpc) 8 | 9 | -- |Run the @sleep@ command. 10 | nvimSleep :: 11 | Member Rpc r => 12 | Int -> 13 | Sem r () 14 | nvimSleep interval = 15 | nvimCommand [exon|sleep #{show interval}|] 16 | 17 | -- |Run the @sleep@ command with the number interpreted as milliseconds. 18 | nvimMSleep :: 19 | Member Rpc r => 20 | Int -> 21 | Sem r () 22 | nvimMSleep interval = 23 | nvimCommand [exon|sleep #{show interval}m|] 24 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Syntax.hs: -------------------------------------------------------------------------------- 1 | -- |Data types and combinators for [Ribosome.Api.Syntax]("Ribosome.Api.Syntax"). 2 | module Ribosome.Syntax ( 3 | module Ribosome.Syntax.Dsl, 4 | module Ribosome.Data.Syntax.SyntaxKind, 5 | module Ribosome.Data.Syntax.Syntax, 6 | module Ribosome.Data.SyntaxItem, 7 | module Ribosome.Syntax.Cons, 8 | build, 9 | Alg, 10 | ) where 11 | 12 | import Ribosome.Data.Syntax.Dsl (Alg) 13 | import Ribosome.Data.Syntax.Syntax 14 | import Ribosome.Data.Syntax.SyntaxKind 15 | import Ribosome.Data.SyntaxItem 16 | import Ribosome.Syntax.Build (build) 17 | import Ribosome.Syntax.Cons 18 | import Ribosome.Syntax.Dsl 19 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Api/Tabpage.hs: -------------------------------------------------------------------------------- 1 | -- |API functions for tabpages. 2 | module Ribosome.Api.Tabpage where 3 | 4 | import Exon (exon) 5 | 6 | import Ribosome.Host.Api.Data (Tabpage) 7 | import Ribosome.Host.Api.Data (nvimTabpageGetNumber, tabpageIsValid, vimCommand) 8 | import Ribosome.Host.Effect.Rpc (Rpc) 9 | import Ribosome.Host.Modify (silentBang) 10 | 11 | -- |Close a tabpage. 12 | closeTabpage :: 13 | Member Rpc r => 14 | Tabpage -> 15 | Sem r () 16 | closeTabpage tabpage = 17 | whenM (tabpageIsValid tabpage) do 18 | number <- nvimTabpageGetNumber tabpage 19 | silentBang do 20 | vimCommand [exon|tabclose! #{show number}|] 21 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/RpcBatch.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Data.RpcBatch where 2 | 3 | import Data.MessagePack (Object) 4 | 5 | import Ribosome.Host.Class.Msgpack.Error (DecodeError) 6 | import Ribosome.Host.Data.Request (Request) 7 | 8 | data RpcBatch :: Type -> Type where 9 | Pure :: a -> RpcBatch a 10 | Bind :: RpcBatch a -> (a -> RpcBatch b) -> RpcBatch b 11 | Request :: Request -> (Object -> Either DecodeError a) -> RpcBatch a 12 | 13 | instance Functor RpcBatch where 14 | fmap f = \case 15 | Pure a -> 16 | Pure (f a) 17 | Bind fa g -> 18 | Bind fa (fmap f . g) 19 | Request req g -> 20 | Request req (fmap f . g) 21 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Api/Normal.hs: -------------------------------------------------------------------------------- 1 | -- |Functions for triggering normal mode commands. 2 | module Ribosome.Api.Normal where 3 | 4 | import Ribosome.Host.Api.Data (nvimCommand) 5 | 6 | import Ribosome.Host.Effect.Rpc (Rpc) 7 | import Exon (exon) 8 | 9 | -- |Execute a sequence of characters in normal mode that may trigger mappings. 10 | normalm :: 11 | Member Rpc r => 12 | Text -> 13 | Sem r () 14 | normalm cmd = 15 | nvimCommand [exon|normal #{cmd}|] 16 | 17 | -- |Execute a sequence of characters in normal mode that may not trigger mappings. 18 | normal :: 19 | Member Rpc r => 20 | Text -> 21 | Sem r () 22 | normal cmd = 23 | nvimCommand [exon|normal! #{cmd}|] 24 | -------------------------------------------------------------------------------- /packages/test/test/fixtures/screenshots/report-5: -------------------------------------------------------------------------------- 1 | 2 | ~ 3 | ~ 4 | ~ 5 | ~ 6 | ~ 7 | ~ 8 | ~ 9 | ~ 10 | ~ 11 | ~ 12 | ~ 13 | ~ 14 | ~ 15 | ~ 16 | ~ 17 | ~ 18 | ~ 19 | ~ 20 | ~ 21 | ~ 22 | ~ 23 | ~ 24 | ~ 25 | ~ 26 | ~ 27 | ~ 28 | ~ 29 | ~ 30 | ~ 31 | ~ 32 | ~ 33 | ~ 34 | ~ 35 | ~ 36 | ~ 37 | ~ 38 | ~ 39 | ~ 40 | ~ 41 | ~ 42 | ~ 43 | ~ 44 | ~ 45 | ~ 46 | ~ 47 | ~ 48 | ~ 49 | ~ 50 | ~ 51 | ~ 52 | ~ 53 | ~ 54 | ~ 55 |  56 | Error detected while processing function StopError: 57 | line 1: 58 | Error invoking 'function:StopError' on channel 3: 59 | report an error Stop by responding 60 | Press ENTER or type command to continue 61 | -------------------------------------------------------------------------------- /packages/test/test/fixtures/screenshots/report-1: -------------------------------------------------------------------------------- 1 | 2 | ~ 3 | ~ 4 | ~ 5 | ~ 6 | ~ 7 | ~ 8 | ~ 9 | ~ 10 | ~ 11 | ~ 12 | ~ 13 | ~ 14 | ~ 15 | ~ 16 | ~ 17 | ~ 18 | ~ 19 | ~ 20 | ~ 21 | ~ 22 | ~ 23 | ~ 24 | ~ 25 | ~ 26 | ~ 27 | ~ 28 | ~ 29 | ~ 30 | ~ 31 | ~ 32 | ~ 33 | ~ 34 | ~ 35 | ~ 36 | ~ 37 | ~ 38 | ~ 39 | ~ 40 | ~ 41 | ~ 42 | ~ 43 | ~ 44 | ~ 45 | ~ 46 | ~ 47 | ~ 48 | ~ 49 | ~ 50 | ~ 51 | ~ 52 | ~ 53 | ~ 54 | ~ 55 | ~ 56 | ~ 57 | ~ 58 | ~ 59 | [No Name] 0,0-1 All 60 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Effect/MenuFilter.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Effect.MenuFilter where 2 | 3 | import Ribosome.Menu.Class.MenuMode (MenuMode) 4 | import Ribosome.Menu.Data.Entry (Entries, Entry) 5 | import Ribosome.Menu.Data.MenuItem (Items, MenuItem) 6 | import Ribosome.Menu.Data.MenuQuery (MenuQuery) 7 | 8 | data FilterJob i :: Type -> Type where 9 | Match :: Int -> MenuItem i -> FilterJob i (Maybe (Int, Entry i)) 10 | Initial :: Items i -> FilterJob i (Entries i) 11 | Refine :: Entries i -> FilterJob i (Entries i) 12 | 13 | data MenuFilter :: Effect where 14 | MenuFilter :: MenuMode i mode => mode -> MenuQuery -> FilterJob i a -> MenuFilter m a 15 | 16 | makeSem ''MenuFilter 17 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Interpreter/PluginName.hs: -------------------------------------------------------------------------------- 1 | -- |Interpreters for @'Reader' ('PluginConfig' c)@ 2 | module Ribosome.Interpreter.PluginName where 3 | 4 | import Ribosome.Data.PluginConfig (PluginConfig (PluginConfig, name)) 5 | import Ribosome.Data.PluginName (PluginName) 6 | 7 | -- |Interpret @'Reader' 'PluginName'@ by extracting the name from the plugin config provided by another @'Reader' 8 | -- 'PluginConfig'@. 9 | -- 10 | -- This interpreter is used by the main function machinery. 11 | interpretPluginName :: 12 | Member (Reader (PluginConfig c)) r => 13 | InterpreterFor (Reader PluginName) r 14 | interpretPluginName sem = 15 | ask >>= \ PluginConfig {name} -> 16 | runReader name sem 17 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Api/Tags.hs: -------------------------------------------------------------------------------- 1 | -- |API functions for tags. 2 | module Ribosome.Api.Tags where 3 | 4 | import Path (Abs, File, Path) 5 | 6 | import Ribosome.Data.Tag (Tag) 7 | import Ribosome.Host.Api.Data (nvimCallFunction) 8 | import Ribosome.Host.Class.Msgpack.Encode (toMsgpack) 9 | import Ribosome.Host.Effect.Rpc (Rpc) 10 | import Ribosome.Host.Path (pathText) 11 | 12 | -- |Return all tags matching the supplied regex (default @.@), optionally prioritizing the supplied file. 13 | taglist :: 14 | Member Rpc r => 15 | Maybe Text -> 16 | Maybe (Path Abs File) -> 17 | Sem r [Tag] 18 | taglist expr file = 19 | nvimCallFunction "taglist" (toMsgpack <$> (fromMaybe "." expr : foldMap (pure . pathText) file)) 20 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/CliConfig.hs: -------------------------------------------------------------------------------- 1 | -- |Config data types for the CLI option parser. 2 | module Ribosome.Data.CliConfig where 3 | 4 | import Log (Severity) 5 | import Path (Abs, File, Path) 6 | 7 | -- |Intermediate data type with optional fields for 'Ribosome.LogConfig'. 8 | data CliLogConfig = 9 | CliLogConfig { 10 | logFile :: Maybe (Path Abs File), 11 | logLevelEcho :: Maybe Severity, 12 | logLevelStderr :: Maybe Severity, 13 | logLevelFile :: Maybe Severity 14 | } 15 | deriving stock (Eq, Show) 16 | 17 | -- |Intermediate data type with optional fields for 'Ribosome.HostConfig'. 18 | data CliConfig = 19 | CliConfig { 20 | log :: CliLogConfig 21 | } 22 | deriving stock (Eq, Show) 23 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Interpreter/UserError.hs: -------------------------------------------------------------------------------- 1 | -- |Interpreters for 'UserError'. 2 | module Ribosome.Interpreter.UserError where 3 | 4 | import Log (Severity (Info)) 5 | 6 | import Ribosome.Data.PluginName (PluginName) 7 | import Ribosome.Host.Effect.UserError (UserError (UserError)) 8 | import Ribosome.PluginName (pluginNamePrefixed) 9 | 10 | -- |Interpret 'UserError' by prefixing messages with the plugin name. 11 | interpretUserErrorPrefixed :: 12 | Member (Reader PluginName) r => 13 | InterpreterFor UserError r 14 | interpretUserErrorPrefixed = 15 | interpret \case 16 | UserError e severity | severity >= Info -> 17 | Just . pure <$> pluginNamePrefixed e 18 | UserError _ _ -> 19 | pure Nothing 20 | -------------------------------------------------------------------------------- /packages/app/lib/Ribosome/App/ProjectNames.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.App.ProjectNames where 2 | 3 | import Path (parseRelDir) 4 | 5 | import Ribosome.App.Data (ModuleName (ModuleName), ProjectName (ProjectName), ProjectNames (..)) 6 | import Ribosome.Host.Text (pascalCase) 7 | 8 | parse :: 9 | IsString err => 10 | String -> 11 | Either err ProjectNames 12 | parse raw = do 13 | let 14 | name = ProjectName (toText raw) 15 | modRaw = pascalCase raw 16 | moduleName = ModuleName (toText modRaw) 17 | nameDir <- maybe err pure (parseRelDir raw) 18 | moduleNameDir <- maybe err pure (parseRelDir modRaw) 19 | pure ProjectNames {..} 20 | where 21 | err = 22 | Left "The project name must be usable as a directory name" 23 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/StoredReport.hs: -------------------------------------------------------------------------------- 1 | -- |Data type that attaches a time stamp to a 'Report'. 2 | module Ribosome.Host.Data.StoredReport where 3 | 4 | import qualified Chronos 5 | import Polysemy.Chronos (ChronosTime) 6 | import qualified Time 7 | 8 | import Ribosome.Host.Data.Report (Report) 9 | 10 | -- |Data type that attaches a time stamp to a 'Report'. 11 | data StoredReport = 12 | StoredReport { 13 | report :: !Report, 14 | time :: !Chronos.Time 15 | } 16 | deriving stock (Show) 17 | 18 | -- |Create a new 'StoredReport' by querying the current time from 'ChronosTime'. 19 | now :: 20 | Member ChronosTime r => 21 | Report -> 22 | Sem r StoredReport 23 | now r = 24 | StoredReport r <$> Time.now 25 | -------------------------------------------------------------------------------- /packages/host/test/Ribosome/Host/Test/CommandParamErrorDecls.hs: -------------------------------------------------------------------------------- 1 | {-# options_ghc -fdefer-type-errors -Wno-deferred-type-errors #-} 2 | 3 | module Ribosome.Host.Test.CommandParamErrorDecls where 4 | 5 | import Data.MessagePack (Object) 6 | 7 | import Ribosome.Host.Data.Args (Args) 8 | import Ribosome.Host.Data.Bang (Bang) 9 | import Ribosome.Host.Data.Report (Report) 10 | import Ribosome.Host.Handler.Command (OptionStateZero, commandOptions) 11 | 12 | argAfterArgs :: 13 | (Map Text Object, [Text]) 14 | argAfterArgs = 15 | commandOptions @OptionStateZero @(Args -> Int -> ()) 16 | 17 | argsAfterArg :: 18 | (Map Text Object, [Text]) 19 | argsAfterArg = 20 | commandOptions @OptionStateZero @(Int -> Bang -> Sem '[Stop Report] ()) 21 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Api/Function.hs: -------------------------------------------------------------------------------- 1 | -- |Defining Neovim functions. 2 | module Ribosome.Api.Function where 3 | 4 | import qualified Data.Text as Text 5 | import Exon (exon) 6 | 7 | import Ribosome.Host.Api.Data (nvimExec) 8 | import Ribosome.Host.Effect.Rpc (Rpc) 9 | 10 | -- |Define a Neovim function. 11 | defineFunction :: 12 | Member Rpc r => 13 | -- |Function name. 14 | Text -> 15 | -- |Function parameters. 16 | [Text] -> 17 | -- |Vimscript lines that form the function body. 18 | [Text] -> 19 | Sem r () 20 | defineFunction name params body = 21 | void $ nvimExec (unlines (sig : body ++ ["endfunction"])) True 22 | where 23 | sig = 24 | [exon|function! #{name}(#{Text.intercalate ", " params})|] 25 | -------------------------------------------------------------------------------- /packages/test/test/fixtures/screenshots/report-2: -------------------------------------------------------------------------------- 1 | 2 | ~ 3 | ~ 4 | ~ 5 | ~ 6 | ~ 7 | ~ 8 | ~ 9 | ~ 10 | ~ 11 | ~ 12 | ~ 13 | ~ 14 | ~ 15 | ~ 16 | ~ 17 | ~ 18 | ~ 19 | ~ 20 | ~ 21 | ~ 22 | ~ 23 | ~ 24 | ~ 25 | ~ 26 | ~ 27 | ~ 28 | ~ 29 | ~ 30 | ~ 31 | ~ 32 | ~ 33 | ~ 34 | ~ 35 | ~ 36 | ~ 37 | ~ 38 | ~ 39 | ~ 40 | ~ 41 | ~ 42 | ~ 43 | ~ 44 | ~ 45 | ~ 46 | ~ 47 | ~ 48 | ~ 49 | ~ 50 | ~ 51 | ~ 52 | ~ 53 | ~ 54 | ~ 55 | ~ 56 | ~ 57 | ~ 58 | ~ 59 | [No Name] 0,0-1 All 60 | test: report Log.info message 61 | -------------------------------------------------------------------------------- /packages/test/test/fixtures/screenshots/report-3: -------------------------------------------------------------------------------- 1 | 2 | ~ 3 | ~ 4 | ~ 5 | ~ 6 | ~ 7 | ~ 8 | ~ 9 | ~ 10 | ~ 11 | ~ 12 | ~ 13 | ~ 14 | ~ 15 | ~ 16 | ~ 17 | ~ 18 | ~ 19 | ~ 20 | ~ 21 | ~ 22 | ~ 23 | ~ 24 | ~ 25 | ~ 26 | ~ 27 | ~ 28 | ~ 29 | ~ 30 | ~ 31 | ~ 32 | ~ 33 | ~ 34 | ~ 35 | ~ 36 | ~ 37 | ~ 38 | ~ 39 | ~ 40 | ~ 41 | ~ 42 | ~ 43 | ~ 44 | ~ 45 | ~ 46 | ~ 47 | ~ 48 | ~ 49 | ~ 50 | ~ 51 | ~ 52 | ~ 53 | ~ 54 | ~ 55 | ~ 56 | ~ 57 | ~ 58 | ~ 59 | [No Name] 0,0-1 All 60 | test: report Report.warn message 61 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/Menu.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.Menu where 2 | 3 | -- import Ribosome.Menu.Data.CursorIndex (CursorIndex) 4 | -- import Ribosome.Menu.Data.State (Modal) 5 | 6 | -- data Menu filter i = 7 | -- Menu { 8 | -- items :: Modal filter i, 9 | -- cursor :: CursorIndex 10 | -- } 11 | -- deriving stock (Eq, Show, Generic) 12 | -- deriving anyclass (Default) 13 | 14 | -- consMenu :: 15 | -- Items i -> 16 | -- Entries i -> 17 | -- Map (mode i) (Trie (Entries i)) -> 18 | -- Int -> 19 | -- Int -> 20 | -- MenuQuery -> 21 | -- filter -> 22 | -- CursorIndex -> 23 | -- Menu mode i 24 | -- consMenu it en hist cnt ecnt curr currF curs = 25 | -- Menu (Modal it en hist cnt ecnt curr currF) curs 26 | -------------------------------------------------------------------------------- /packages/test/test/fixtures/screenshots/report-4: -------------------------------------------------------------------------------- 1 | 2 | ~ 3 | ~ 4 | ~ 5 | ~ 6 | ~ 7 | ~ 8 | ~ 9 | ~ 10 | ~ 11 | ~ 12 | ~ 13 | ~ 14 | ~ 15 | ~ 16 | ~ 17 | ~ 18 | ~ 19 | ~ 20 | ~ 21 | ~ 22 | ~ 23 | ~ 24 | ~ 25 | ~ 26 | ~ 27 | ~ 28 | ~ 29 | ~ 30 | ~ 31 | ~ 32 | ~ 33 | ~ 34 | ~ 35 | ~ 36 | ~ 37 | ~ 38 | ~ 39 | ~ 40 | ~ 41 | ~ 42 | ~ 43 | ~ 44 | ~ 45 | ~ 46 | ~ 47 | ~ 48 | ~ 49 | ~ 50 | ~ 51 | ~ 52 | ~ 53 | ~ 54 | ~ 55 | ~ 56 | ~ 57 | ~ 58 | ~ 59 | [No Name] 0,0-1 All 60 | test: report an info Stop by echoing 61 | -------------------------------------------------------------------------------- /packages/test/test/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Polysemy.Test (unitTest) 4 | import Ribosome.Test.EmbedTmuxTest (test_embedTmux) 5 | import Ribosome.Test.ReportTest (test_report) 6 | import Ribosome.Test.Skip (skipUnlessX) 7 | import Ribosome.Test.SocketTmuxTest (test_socketTmux) 8 | import Ribosome.Test.SyntaxTest (test_syntax) 9 | import Test.Tasty (TestTree, defaultMain, testGroup) 10 | 11 | tests :: TestTree 12 | tests = 13 | testGroup "ribosome" [ 14 | unitTest "socket tmux" (skipUnlessX test_socketTmux), 15 | unitTest "embed tmux" (skipUnlessX test_embedTmux), 16 | unitTest "syntax" (skipUnlessX test_syntax), 17 | unitTest "report" (skipUnlessX test_report) 18 | ] 19 | 20 | main :: IO () 21 | main = 22 | defaultMain tests 23 | -------------------------------------------------------------------------------- /packages/integration/test/fixtures/plugin/packages/test-plugin/test-plugin.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.2 2 | name: test-plugin 3 | version: 0 4 | synopsis: Internal project for testing ribosome 5 | 6 | executable test-plugin 7 | main-is: Main.hs 8 | hs-source-dirs: 9 | app 10 | ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Widentities -Wunused-packages -fplugin=Polysemy.Plugin -threaded -rtsopts -with-rtsopts=-N 11 | build-depends: 12 | base >=4.12 && <5 13 | , incipit 14 | , integration 15 | , polysemy 16 | , polysemy-plugin 17 | mixins: 18 | base hiding (Prelude) 19 | , incipit hiding (Incipit) 20 | , incipit (Incipit as Prelude) 21 | default-language: Haskell2010 22 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Text.hs: -------------------------------------------------------------------------------- 1 | -- |Combinators for 'Text'. 2 | module Ribosome.Text where 3 | 4 | import Data.Char (toUpper) 5 | import qualified Data.Text as Text 6 | 7 | -- |Escape a single quote Neovim-style by replacing it with two single quotes. 8 | escapeQuote :: Char -> Text 9 | escapeQuote = \case 10 | '\'' -> 11 | "''" 12 | a -> 13 | Text.singleton a 14 | 15 | -- |Escape all single quotes Neovim-style by replacing them with two single quotes. 16 | escapeQuotes :: Text -> Text 17 | escapeQuotes = 18 | Text.concatMap escapeQuote 19 | 20 | -- |Upcase the first letter of a 'Text', if any. 21 | capitalize :: Text -> Text 22 | capitalize a = 23 | maybe "" run (Text.uncons a) 24 | where 25 | run (h, t) = 26 | Text.cons (toUpper h) t 27 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Api/Event.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Api.Event where 2 | 3 | import Ribosome.Host.Api.Data (Buffer) 4 | import Ribosome.Host.Class.Msgpack.Array (msgpackArray) 5 | import Ribosome.Host.Class.Msgpack.Decode (pattern Msgpack) 6 | import Ribosome.Host.Data.Event (Event (Event)) 7 | 8 | pattern BufLinesEvent :: Buffer -> Maybe Int -> Int -> Int -> [Text] -> Bool -> Event 9 | pattern BufLinesEvent {buffer, changedtick, firstline, lastline, linedata, more} <- Event "nvim_buf_lines_event" [ 10 | Msgpack buffer, 11 | Msgpack changedtick, 12 | Msgpack firstline, 13 | Msgpack lastline, 14 | Msgpack linedata, 15 | Msgpack more 16 | ] where 17 | BufLinesEvent b c f l ld m = 18 | Event "nvim_buf_lines_event" (msgpackArray b c f l ld m) 19 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/Response.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Data.Response where 2 | 3 | import Data.MessagePack (Object) 4 | import Exon (exon) 5 | 6 | import Ribosome.Host.Data.Request (RequestId (RequestId)) 7 | 8 | data Response = 9 | Success Object 10 | | 11 | Error Text 12 | deriving stock (Eq, Show) 13 | 14 | formatResponse :: Response -> Text 15 | formatResponse = \case 16 | Success o -> show o 17 | Error e -> [exon|error: #{e}|] 18 | 19 | data TrackedResponse = 20 | TrackedResponse { 21 | id :: RequestId, 22 | payload :: Response 23 | } 24 | deriving stock (Eq, Show) 25 | 26 | formatTrackedResponse :: TrackedResponse -> Text 27 | formatTrackedResponse (TrackedResponse (RequestId i) payload) = 28 | [exon|<#{show i}> #{formatResponse payload}|] 29 | -------------------------------------------------------------------------------- /packages/app/lib/Ribosome/App/Templates/PingTestHs.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.App.Templates.PingTestHs where 2 | 3 | import Exon (exon) 4 | 5 | import Ribosome.App.Data (ModuleName (ModuleName)) 6 | 7 | pingTestHs :: ModuleName -> Text 8 | pingTestHs (ModuleName modName) = 9 | [exon|module #{modName}.Test.PingTest where 10 | 11 | import Polysemy.Test (UnitTest, (===)) 12 | import Ribosome.Api (nvimCallFunction) 13 | import Ribosome.Test (testPlugin) 14 | 15 | import #{modName}.Plugin (#{modName}Stack, handlers, interpret#{modName}Stack) 16 | 17 | test_ping :: UnitTest 18 | test_ping = 19 | testPlugin @#{modName}Stack interpret#{modName}Stack handlers do 20 | r <- call *> call *> call 21 | (3 :: Int) === r 22 | where 23 | call = 24 | nvimCallFunction "#{modName}Ping" [] 25 | |] 26 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/SyntaxItem.hs: -------------------------------------------------------------------------------- 1 | -- |Data types for syntax items. 2 | module Ribosome.Data.SyntaxItem where 3 | 4 | import Ribosome.Data.Syntax.SyntaxKind (SyntaxKind) 5 | 6 | -- |The identifier for a syntax item. 7 | newtype SyntaxGroup = 8 | SyntaxGroup { unSyntaxGroup :: Text } 9 | deriving stock (Eq, Show, Generic) 10 | deriving newtype (IsString, Ord, Semigroup, Monoid) 11 | 12 | -- |A syntax item like @keyword@ or @match@, bundled with options for the @:syntax@ command. 13 | data SyntaxItem = 14 | SyntaxItem { 15 | group :: SyntaxGroup, 16 | kind :: SyntaxKind, 17 | options :: [Text], 18 | params :: Map Text Text, 19 | next :: [SyntaxGroup], 20 | contains :: [SyntaxGroup], 21 | contained :: Bool 22 | } 23 | deriving stock (Eq, Show, Generic) 24 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Interpreter/Process/Stdio.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Interpreter.Process.Stdio where 2 | 3 | import Data.Serialize (Serialize) 4 | import Polysemy.Process (Process, interpretProcessCurrent) 5 | 6 | import Ribosome.Host.Data.BootError (BootError (BootError)) 7 | import Ribosome.Host.IOStack (IOStack) 8 | import Ribosome.Host.Interpreter.Process.Cereal (interpretProcessInputCereal, interpretProcessOutputCereal) 9 | 10 | interpretProcessCerealStdio :: 11 | Serialize a => 12 | Members IOStack r => 13 | InterpreterFor (Process a (Either Text a)) r 14 | interpretProcessCerealStdio = 15 | interpretProcessOutputCereal . 16 | interpretProcessInputCereal . 17 | interpretProcessCurrent def . 18 | raiseUnder2 . 19 | resumeHoistError (BootError . show @Text) . 20 | raiseUnder 21 | -------------------------------------------------------------------------------- /packages/test/lib/Ribosome/Test/Examples/Example1.hs: -------------------------------------------------------------------------------- 1 | {-# options_haddock prune, hide #-} 2 | 3 | -- |An example for the docs. 4 | module Ribosome.Test.Examples.Example1 where 5 | 6 | import Polysemy.Test 7 | 8 | import Ribosome 9 | import Ribosome.Api 10 | import Ribosome.Test 11 | 12 | store :: 13 | Member (Rpc !! RpcError) r => 14 | Args -> 15 | Handler r () 16 | store (Args msg) = 17 | ignoreRpcError do 18 | nvimSetVar "message" msg 19 | 20 | test_direct :: UnitTest 21 | test_direct = 22 | testEmbed_ do 23 | store "test directly" 24 | assertEq "test directly" =<< nvimGetVar @Text "message" 25 | 26 | test_rpc :: UnitTest 27 | test_rpc = 28 | testPlugin_ [rpcCommand "Store" Sync store] do 29 | nvimCommand "Store test RPC" 30 | assertEq "test RPC" =<< nvimGetVar @Text "message" 31 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/MenuView.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.MenuView where 2 | 3 | import Ribosome.Menu.Data.CursorIndex (CursorIndex) 4 | import Ribosome.Menu.Data.CursorLine (CursorLine) 5 | 6 | newtype EntryIndex = 7 | EntryIndex Word 8 | deriving stock (Eq, Show, Generic) 9 | deriving newtype (Num, Real, Enum, Integral, Ord) 10 | 11 | data ViewRange = 12 | ViewRange { 13 | bottom :: EntryIndex, 14 | top :: EntryIndex, 15 | cursorLine :: CursorLine, 16 | entryLines :: [(EntryIndex, CursorLine)] 17 | } 18 | deriving stock (Eq, Show, Generic) 19 | 20 | data MenuView = 21 | MenuView { 22 | range :: Maybe ViewRange, 23 | cursor :: CursorIndex 24 | } 25 | deriving stock (Eq, Show, Generic) 26 | 27 | instance Default MenuView where 28 | def = 29 | MenuView Nothing 0 30 | -------------------------------------------------------------------------------- /packages/integration/lib/Integration.hs: -------------------------------------------------------------------------------- 1 | module Integration where 2 | 3 | import Data.MessagePack (Object (ObjectInt)) 4 | import Log (Severity (Debug)) 5 | import Ribosome.Data.PluginConfig (PluginConfig (PluginConfig)) 6 | import Ribosome.Host.Data.Execution (Execution (Sync)) 7 | import Ribosome.Host.Data.HostConfig (setStderr) 8 | import Ribosome.Host.Data.RpcHandler (RpcHandler (RpcHandler)) 9 | import qualified Ribosome.Host.Data.RpcType as RpcType 10 | import Ribosome.Remote (runNvimPluginIO_) 11 | 12 | hand :: 13 | [Object] -> 14 | Sem r Object 15 | hand _ = 16 | pure (ObjectInt 5) 17 | 18 | handlers :: 19 | [RpcHandler r] 20 | handlers = 21 | [RpcHandler RpcType.Function "Test" Sync hand] 22 | 23 | integrationTest :: IO () 24 | integrationTest = 25 | runNvimPluginIO_ (PluginConfig "int" (setStderr Debug def) unit) handlers 26 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Scratch.hs: -------------------------------------------------------------------------------- 1 | -- |A scratch buffer is what Neovim calls text not associated with a file, used for informational or interactive 2 | -- content. 3 | -- 4 | -- Ribosome provides an interface for maintaining those, by associating a view configuration with an ID and allowing to 5 | -- update the text displayed in it. 6 | module Ribosome.Scratch ( 7 | module Ribosome.Effect.Scratch, 8 | module Ribosome.Data.ScratchOptions, 9 | module Ribosome.Data.ScratchId, 10 | module Ribosome.Data.ScratchState, 11 | module Ribosome.Data.FloatOptions, 12 | module Ribosome.Interpreter.Scratch, 13 | ) where 14 | 15 | import Ribosome.Data.FloatOptions 16 | import Ribosome.Data.ScratchId 17 | import Ribosome.Data.ScratchOptions 18 | import Ribosome.Data.ScratchState 19 | import Ribosome.Effect.Scratch 20 | import Ribosome.Interpreter.Scratch 21 | -------------------------------------------------------------------------------- /packages/ribosome/test/Ribosome/Test/UndoTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Test.UndoTest where 2 | 3 | import Polysemy.Test (UnitTest, assertEq) 4 | 5 | import Ribosome.Api.Buffer (currentBufferContent, setCurrentBufferContent) 6 | import Ribosome.Api.Register (setregLine, unnamedRegister) 7 | import Ribosome.Api.Window (setCurrentCursor) 8 | import Ribosome.Host.Api.Data (nvimCommand) 9 | import Ribosome.Host.Test.Run (embedTest_) 10 | 11 | test_undo :: UnitTest 12 | test_undo = 13 | embedTest_ do 14 | setCurrentBufferContent orig 15 | setCurrentCursor 1 0 16 | setregLine unnamedRegister new 17 | nvimCommand "normal! p" 18 | assertEq (orig <> new) =<< currentBufferContent 19 | nvimCommand "undo" 20 | assertEq orig =<< currentBufferContent 21 | where 22 | orig = 23 | ["line1", "line2"] 24 | new = 25 | ["new1", "new2"] 26 | -------------------------------------------------------------------------------- /ops/boot.nix: -------------------------------------------------------------------------------- 1 | { self, config, ... }: 2 | let 3 | 4 | githubOrg = 5 | if config.githubOrg == null 6 | then "" 7 | else "--github-org ${config.githubOrg}"; 8 | 9 | githubRepo = 10 | if config.githubRepo == null 11 | then "" 12 | else "--github-repo ${config.githubRepo}"; 13 | 14 | cachixName = 15 | if config.cachixName == null 16 | then "" 17 | else "--cachix ${config.cachixName}"; 18 | 19 | cachixKey = 20 | if config.cachixKey == null 21 | then "" 22 | else "--cachix-key ${config.cachixKey}"; 23 | 24 | branch = "--branch ${config.branch}"; 25 | 26 | script = config.pkgs.writeScript "ribosome-regen-boot" '' 27 | #!${config.pkgs.zsh}/bin/zsh 28 | nix run path:${self}#ribosome -- boot ${config.exe} ${githubOrg} ${githubRepo} ${cachixName} ${cachixKey} ${branch} "$@" 29 | ''; 30 | 31 | in script 32 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Effect/UserError.hs: -------------------------------------------------------------------------------- 1 | -- |The effect 'UserError' decides which messages to display in Neovim. 2 | module Ribosome.Host.Effect.UserError where 3 | 4 | import Polysemy.Log (Severity) 5 | 6 | -- |The effect 'UserError' decides which messages to display in Neovim. 7 | -- 8 | -- Additionally, the text may be manipulated, which is done by the interpreter in "Ribosome", which prefixes the message 9 | -- with the plugin name. 10 | data UserError :: Effect where 11 | -- |Decide whether and how to display the given message at the given log level. 12 | UserError :: Text -> Severity -> UserError m (Maybe [Text]) 13 | 14 | makeSem_ ''UserError 15 | 16 | -- |Decide whether and how to display the given message at the given log level. 17 | userError :: 18 | Member UserError r => 19 | Text -> 20 | Severity -> 21 | Sem r (Maybe [Text]) 22 | -------------------------------------------------------------------------------- /packages/test/test/fixtures/screenshots/syntax: -------------------------------------------------------------------------------- 1 | function :: String -> Int 2 | function _ = 5 3 | S A a E 1 4 | S B E 2 5 | ~ 6 | ~ 7 | ~ 8 | ~ 9 | ~ 10 | ~ 11 | ~ 12 | ~ 13 | ~ 14 | ~ 15 | ~ 16 | ~ 17 | ~ 18 | ~ 19 | ~ 20 | ~ 21 | ~ 22 | ~ 23 | ~ 24 | ~ 25 | ~ 26 | ~ 27 | ~ 28 | ~ 29 | ~ 30 | ~ 31 | ~ 32 | ~ 33 | ~ 34 | ~ 35 | ~ 36 | ~ 37 | ~ 38 | ~ 39 | ~ 40 | ~ 41 | ~ 42 | ~ 43 | ~ 44 | ~ 45 | ~ 46 | ~ 47 | ~ 48 | ~ 49 | ~ 50 | ~ 51 | ~ 52 | ~ 53 | ~ 54 | ~ 55 | ~ 56 | ~ 57 | ~ 58 | ~ 59 | [No Name] [+] 1,1 All 60 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Effect/Reports.hs: -------------------------------------------------------------------------------- 1 | -- |An effect for storing errors. 2 | module Ribosome.Host.Effect.Reports where 3 | 4 | import Ribosome.Host.Data.Report (Report, ReportContext) 5 | import Ribosome.Host.Data.StoredReport (StoredReport) 6 | 7 | -- |This internal effect stores all errors in memory that have been created through the 'Report' system. 8 | data Reports :: Effect where 9 | -- |Add a report to the store. 10 | StoreReport :: ReportContext -> Report -> Reports m () 11 | -- |Get all reports. 12 | StoredReports :: Reports m (Map ReportContext [StoredReport]) 13 | 14 | makeSem_ ''Reports 15 | 16 | -- |Add a report to the store. 17 | storeReport :: 18 | Member Reports r => 19 | ReportContext -> 20 | Report -> 21 | Sem r () 22 | 23 | -- |Get all reports. 24 | storedReports :: 25 | Member Reports r => 26 | Sem r (Map ReportContext [StoredReport]) 27 | -------------------------------------------------------------------------------- /packages/test/lib/Ribosome/Test/Data/TestConfig.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Test.Data.TestConfig where 2 | 3 | import qualified Chiasma.Test.Data.TmuxTestConfig as Chiasma 4 | 5 | import Ribosome.Data.PluginConfig (PluginConfig (PluginConfig)) 6 | import Ribosome.Host.Data.HostConfig (HostConfig (HostConfig), dataLogConc) 7 | 8 | data TestConfig = 9 | TestConfig { 10 | freeze :: Bool, 11 | plugin :: PluginConfig () 12 | } 13 | deriving stock (Eq, Show, Generic) 14 | 15 | instance Default TestConfig where 16 | def = 17 | TestConfig False (PluginConfig "test" (HostConfig def { dataLogConc = False }) unit) 18 | 19 | data TmuxTestConfig = 20 | TmuxTestConfig { 21 | core :: TestConfig, 22 | tmux :: Chiasma.TmuxTestConfig 23 | } 24 | deriving stock (Eq, Show, Generic) 25 | 26 | instance Default TmuxTestConfig where 27 | def = 28 | TmuxTestConfig def def { Chiasma.gui = False } 29 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/Execution.hs: -------------------------------------------------------------------------------- 1 | -- |RPC message execution 2 | module Ribosome.Host.Data.Execution where 3 | 4 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode (fromMsgpack)) 5 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode (toMsgpack)) 6 | 7 | -- |This type indicates the execution style that Neovim should be instructed to use for RPC messages – synchronous 8 | -- requests that block Neovim until a result is returned and asynchronous notifications. 9 | -- 10 | -- 11 | data Execution = 12 | -- |RPC Request 13 | Sync 14 | | 15 | -- |RPC Notification 16 | Async 17 | deriving stock (Eq, Show, Enum, Bounded) 18 | 19 | instance MsgpackEncode Execution where 20 | toMsgpack exec = 21 | toMsgpack (exec == Sync) 22 | 23 | instance MsgpackDecode Execution where 24 | fromMsgpack o = 25 | fromMsgpack o <&> \case 26 | True -> Sync 27 | False -> Async 28 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Settings.hs: -------------------------------------------------------------------------------- 1 | -- |The 'Settings' API, an effect for accessing Neovim variables with defaults. 2 | module Ribosome.Settings ( 3 | module Ribosome.Data.Setting, 4 | module Ribosome.Data.SettingError, 5 | module Ribosome.Settings, 6 | module Ribosome.Effect.Settings, 7 | ) where 8 | 9 | import Ribosome.Data.Setting (Setting (..)) 10 | import Ribosome.Data.SettingError (SettingError (..)) 11 | import Ribosome.Effect.Settings (Settings, get, maybe, or, update) 12 | 13 | -- |The vertical margin for floating windows used by @ribosome-menu@. 14 | menuMarginVertical :: Setting Float 15 | menuMarginVertical = 16 | Setting "ribosome_menu_margin_vertical" False (Just 0.2) 17 | 18 | -- |The horizontal margin for floating windows used by @ribosome-menu@. 19 | menuMarginHorizontal :: Setting Float 20 | menuMarginHorizontal = 21 | Setting "ribosome_menu_margin_horizontal" False (Just 0.1) 22 | -------------------------------------------------------------------------------- /packages/ribosome/test/Ribosome/Test/BufferTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Test.BufferTest where 2 | 3 | import Path (relfile) 4 | import qualified Polysemy.Test as Test 5 | import Polysemy.Test (UnitTest, assert, assertEq, evalMaybe, (/==), (===)) 6 | 7 | import Ribosome.Api.Buffer (bufferCount, bufferForFile, bufferIsFile, edit) 8 | import Ribosome.Data.FileBuffer (FileBuffer (FileBuffer)) 9 | import Ribosome.Host.Api.Data (nvimGetCurrentBuf) 10 | import Ribosome.Host.Test.Run (embedTest_) 11 | 12 | test_bufferForFile :: UnitTest 13 | test_bufferForFile = 14 | embedTest_ do 15 | file1 <- Test.tempFile [] [relfile|file.x|] 16 | file2 <- Test.tempFile [] [relfile|file.y|] 17 | edit file1 18 | edit file2 19 | assertEq 2 =<< bufferCount 20 | cb <- nvimGetCurrentBuf 21 | assert =<< bufferIsFile cb 22 | FileBuffer buf path <- evalMaybe =<< bufferForFile file1 23 | cb /== buf 24 | path === file1 25 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/Syntax/Syntax.hs: -------------------------------------------------------------------------------- 1 | -- |Data types for the Neovim syntax API. 2 | module Ribosome.Data.Syntax.Syntax where 3 | 4 | import Ribosome.Data.SyntaxItem (SyntaxGroup, SyntaxItem) 5 | 6 | -- |Options for a highlight group. 7 | data Highlight = 8 | Highlight { 9 | group :: SyntaxGroup, 10 | values :: Map Text Text 11 | } 12 | deriving stock (Eq, Show, Generic) 13 | 14 | -- |Options for a @:highlight link@ command. 15 | data HiLink = 16 | HiLink { 17 | group :: SyntaxGroup, 18 | target :: SyntaxGroup 19 | } 20 | deriving stock (Eq, Show, Generic) 21 | 22 | -- |A set of syntax settings, consisting of syntax items like @keyword@ and @match@, highlights and highlight links. 23 | data Syntax = 24 | Syntax { 25 | items :: [SyntaxItem], 26 | highlights :: [Highlight], 27 | links :: [HiLink] 28 | } 29 | deriving stock (Eq, Show, Generic) 30 | deriving anyclass (Default) 31 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/Syntax/Dsl.hs: -------------------------------------------------------------------------------- 1 | {-# options_haddock prune #-} 2 | 3 | -- |The syntax DSL algebra. 4 | module Ribosome.Data.Syntax.Dsl where 5 | 6 | import Ribosome.Data.SyntaxItem (SyntaxGroup, SyntaxItem) 7 | 8 | -- |The syntax DSL algebra. 9 | data Alg :: Type where 10 | Item :: SyntaxItem -> Alg 11 | Choice :: Alg -> Alg -> Alg 12 | Chain :: Alg -> Alg -> Alg 13 | Contain :: Alg -> Alg -> Alg 14 | Prefix :: SyntaxGroup -> Alg -> Alg 15 | Hi :: Map Text Text -> Alg -> Alg 16 | Link :: SyntaxGroup -> Alg -> Alg 17 | Mod :: (SyntaxItem -> SyntaxItem) -> Alg -> Alg 18 | 19 | data Building = 20 | Building { 21 | prefix :: SyntaxGroup, 22 | contained :: Bool, 23 | next :: [SyntaxGroup], 24 | contains :: [SyntaxGroup], 25 | modItem :: SyntaxItem -> SyntaxItem 26 | } 27 | deriving stock (Generic) 28 | 29 | instance Default Building where 30 | def = 31 | Building "" False [] [] id 32 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/PluginName.hs: -------------------------------------------------------------------------------- 1 | -- |Combinators for 'PluginName'. 2 | module Ribosome.PluginName where 3 | 4 | import Exon (exon) 5 | 6 | import Ribosome.Data.PluginName (PluginName (PluginName)) 7 | import Ribosome.Host.Text (pascalCase) 8 | 9 | -- |Get the 'PluginName' from a 'Reader'. 10 | pluginName :: 11 | Member (Reader PluginName) r => 12 | Sem r PluginName 13 | pluginName = 14 | ask 15 | 16 | -- |Get the 'PluginName' from a 'Reader' and prefix the given string with it, followed by a colon.. 17 | pluginNamePrefixed :: 18 | Member (Reader PluginName) r => 19 | Text -> 20 | Sem r Text 21 | pluginNamePrefixed msg = do 22 | PluginName name <- pluginName 23 | pure [exon|#{name}: #{msg}|] 24 | 25 | -- |Get the 'PluginName' from a 'Reader' and convert it to PascalCase. 26 | pluginNamePascalCase :: 27 | Member (Reader PluginName) r => 28 | Sem r PluginName 29 | pluginNamePascalCase = do 30 | PluginName n <- pluginName 31 | pure (PluginName (pascalCase n)) 32 | -------------------------------------------------------------------------------- /packages/ribosome/test/Ribosome/Test/PersistTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Test.PersistTest where 2 | 3 | import Path (reldir) 4 | import qualified Polysemy.Test as Test 5 | import Polysemy.Test (UnitTest, assertJust) 6 | 7 | import qualified Ribosome.Effect.Persist as Persist 8 | import Ribosome.Host.Test.Run (embedTest_) 9 | import Ribosome.Interpreter.Persist (interpretPersist) 10 | import Ribosome.Interpreter.PersistPath (interpretPersistPathAt) 11 | import Ribosome.Test.Error (resumeTestError) 12 | 13 | data Thing = 14 | Thing { 15 | number :: Int, 16 | name :: Text 17 | } 18 | deriving stock (Eq, Show, Generic) 19 | deriving anyclass (FromJSON, ToJSON) 20 | 21 | thing :: Thing 22 | thing = 23 | Thing 13 "thingo" 24 | 25 | test_persist :: UnitTest 26 | test_persist = 27 | embedTest_ do 28 | dir <- Test.tempDir [reldir|persist|] 29 | interpretPersistPathAt False dir $ interpretPersist "thing" $ resumeTestError do 30 | Persist.store Nothing thing 31 | assertJust thing =<< Persist.load Nothing 32 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'push' 3 | 4 | on: 5 | push: 6 | branches: 7 | - '**' 8 | 9 | jobs: 10 | release: 11 | name: 'Test and push to Cachix' 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: DeterminateSystems/nix-installer-action@main 16 | with: 17 | extra-conf: | 18 | access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} 19 | extra-substituters = https://tek.cachix.org 20 | extra-trusted-public-keys = tek.cachix.org-1:+sdc73WFq8aEKnrVv5j/kuhmnW2hQJuqdPJF5SnaCBk= 21 | - uses: DeterminateSystems/magic-nix-cache-action@main 22 | - uses: cachix/cachix-action@v12 23 | with: 24 | name: tek 25 | signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' 26 | - name: 'build' 27 | env: 28 | RIBOSOME_ROOT: ${{ vars.GITHUB_WORKSPACE }} 29 | run: nix flake check 30 | - name: 'template test' 31 | run: nix run .#template-test 32 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/Event.hs: -------------------------------------------------------------------------------- 1 | -- |Events sent from Neovim to the host. 2 | module Ribosome.Host.Data.Event where 3 | 4 | import Data.MessagePack (Object) 5 | 6 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode) 7 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode) 8 | 9 | -- |The name of an event, which corresponds to the RPC method in the payload. 10 | newtype EventName = 11 | EventName { unEventName :: Text } 12 | deriving stock (Eq, Show) 13 | deriving newtype (IsString, Ord, MsgpackDecode, MsgpackEncode) 14 | 15 | -- |An event is an RPC notification sent by Neovim that is not intended to be dispatched to a named handler, but 16 | -- consumed in a broadcasting fashion. 17 | -- 18 | -- Since they aren't marked as such, the host treats any notification with an unknown method name as an event. 19 | -- 20 | -- Events can be consumed with 'Conc.Consume' and 'Conc.subscribe'. 21 | data Event = 22 | Event { 23 | name :: EventName, 24 | payload :: [Object] 25 | } 26 | deriving stock (Eq, Show) 27 | -------------------------------------------------------------------------------- /packages/test/lib/Ribosome/Test/Skip.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Test.Skip where 2 | 3 | import qualified Data.Text.IO as Text 4 | import Polysemy.Test (UnitTest) 5 | import System.Environment (lookupEnv) 6 | import Test.Tasty (TestTree, withResource) 7 | 8 | displayPresent :: IO Bool 9 | displayPresent = isJust <$> lookupEnv "DISPLAY" 10 | 11 | displayMessage :: IO () 12 | displayMessage = Text.putStrLn "Skipping test due to lack of display" 13 | 14 | skipUnlessX :: UnitTest -> UnitTest 15 | skipUnlessX test = do 16 | liftIO displayPresent >>= \case 17 | True -> test 18 | False -> liftIO displayMessage 19 | 20 | displayPresentVerbose :: IO Bool 21 | displayPresentVerbose = 22 | displayPresent >>= tap \case 23 | True -> unit 24 | False -> displayMessage 25 | 26 | requireX :: 27 | (UnitTest -> TestTree) -> 28 | UnitTest -> 29 | TestTree 30 | requireX mkTest test = 31 | withResource displayPresentVerbose (const unit) \ res -> 32 | mkTest do 33 | liftIO res >>= \case 34 | True -> test 35 | False -> unit 36 | -------------------------------------------------------------------------------- /packages/ribosome/test/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Polysemy.Test (unitTest) 4 | import Ribosome.Test.BufferTest (test_bufferForFile) 5 | import Ribosome.Test.MappingTest (test_mapping) 6 | import Ribosome.Test.PersistTest (test_persist) 7 | import Ribosome.Test.ScratchTest (test_scratch) 8 | import Ribosome.Test.SettingTest (test_settings) 9 | import Ribosome.Test.UndoTest (test_undo) 10 | import Ribosome.Test.VariableTest (test_variable) 11 | import Ribosome.Test.WatcherTest (test_varWatcher) 12 | import Ribosome.Test.WindowTest (test_window) 13 | import Test.Tasty (TestTree, defaultMain, testGroup) 14 | 15 | tests :: TestTree 16 | tests = 17 | testGroup "ribosome" [ 18 | unitTest "buffer for file" test_bufferForFile, 19 | test_scratch, 20 | unitTest "mapping" test_mapping, 21 | unitTest "variable watcher" test_varWatcher, 22 | unitTest "persist" test_persist, 23 | test_window, 24 | test_settings, 25 | unitTest "undo" test_undo, 26 | test_variable 27 | ] 28 | 29 | main :: IO () 30 | main = 31 | defaultMain tests 32 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Effect/MenuUi.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Effect.MenuUi where 2 | 3 | import Ribosome.Data.ScratchState (ScratchState) 4 | import Ribosome.Host.Data.RpcError (RpcError) 5 | import Ribosome.Menu.Data.RenderMenu (RenderMenu) 6 | import Ribosome.Menu.Prompt.Data.Prompt (Prompt) 7 | import Ribosome.Menu.Prompt.Data.PromptEvent (PromptEvent) 8 | 9 | data MenuUi :: Effect where 10 | RenderPrompt :: Bool -> Prompt -> MenuUi m () 11 | PromptEvent :: MenuUi m PromptEvent 12 | Render :: RenderMenu i -> MenuUi m () 13 | PromptScratch :: MenuUi m ScratchState 14 | StatusScratch :: MenuUi m ScratchState 15 | ItemsScratch :: MenuUi m ScratchState 16 | 17 | makeSem ''MenuUi 18 | 19 | data WindowMenu = 20 | WindowMenu { 21 | itemsScratch :: ScratchState, 22 | statusScratch :: Maybe ScratchState, 23 | promptScratch :: ScratchState 24 | } 25 | deriving stock (Eq, Show) 26 | 27 | data PureMenu = 28 | PureMenu 29 | deriving stock (Eq, Show) 30 | 31 | type ScopedMenuUi param = 32 | Scoped param (MenuUi !! RpcError) !! RpcError 33 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Run.hs: -------------------------------------------------------------------------------- 1 | -- |Convenience aliases for plugin effects. 2 | module Ribosome.Run where 3 | 4 | import Ribosome.Data.PluginName (PluginName) 5 | import Ribosome.Data.SettingError (SettingError) 6 | import Ribosome.Effect.Scratch (Scratch) 7 | import Ribosome.Effect.Settings (Settings) 8 | import Ribosome.Effect.VariableWatcher (VariableWatcher) 9 | import Ribosome.Host.Data.Report (Report) 10 | import Ribosome.Host.Data.RpcError (RpcError) 11 | import Ribosome.Host.Effect.Handlers (Handlers) 12 | import Ribosome.Host.Effect.Rpc (Rpc) 13 | 14 | -- |The set of core effects that are intepreted by the main logic, minus what's in "Ribosome.Host". 15 | type PluginEffects = 16 | [ 17 | Scratch !! RpcError, 18 | Settings !! SettingError, 19 | VariableWatcher !! Report, 20 | Handlers !! Report 21 | ] 22 | 23 | -- |The set of core effects that handlers and API functions commonly use. 24 | type NvimPlugin = 25 | [ 26 | Scratch !! RpcError, 27 | Settings !! SettingError, 28 | Rpc !! RpcError, 29 | Reader PluginName 30 | ] 31 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Error.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Error where 2 | 3 | import Ribosome.Host.Data.BootError (BootError (BootError)) 4 | import Ribosome.Host.Data.RpcError (RpcError) 5 | import Ribosome.Host.Effect.Rpc (Rpc) 6 | 7 | -- |Run a 'Sem' that uses 'Rpc' and discard 'RpcError's, interpreting 'Rpc' to @'Rpc' '!!' 'RpcError'@. 8 | ignoreRpcError :: 9 | Member (Rpc !! RpcError) r => 10 | Sem (Rpc : r) a -> 11 | Sem r () 12 | ignoreRpcError = 13 | resume_ . void 14 | 15 | -- |Run a 'Sem' that uses 'Rpc' and catch 'RpcError's with the supplied function, interpreting 'Rpc' to @'Rpc' '!!' 16 | -- 'RpcError'@. 17 | onRpcError :: 18 | Member (Rpc !! RpcError) r => 19 | (RpcError -> Sem r a) -> 20 | Sem (Rpc : r) a -> 21 | Sem r a 22 | onRpcError = 23 | resuming 24 | 25 | -- |Resume an error by transforming it to @'Error' 'BootError'@. 26 | resumeBootError :: 27 | ∀ eff err r . 28 | Show err => 29 | Members [eff !! err, Error BootError] r => 30 | InterpreterFor eff r 31 | resumeBootError = 32 | resumeHoistError @_ @eff (BootError . show @Text) 33 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Interpreter/MenuFilter.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Interpreter.MenuFilter where 2 | 3 | import qualified Ribosome.Menu.Class.MenuMode as MenuMode 4 | import Ribosome.Menu.Class.MenuMode (Matcher) 5 | import Ribosome.Menu.Data.Entry (Entry (Entry)) 6 | import Ribosome.Menu.Data.MenuItem (MenuItem) 7 | import Ribosome.Menu.Effect.MenuFilter (FilterJob (Initial, Match, Refine), MenuFilter (MenuFilter)) 8 | import Ribosome.Menu.Filters (initPar, refinePar) 9 | 10 | execute :: 11 | ∀ i t a . 12 | (MenuItem i -> Maybe t) -> 13 | Matcher i t -> 14 | FilterJob i a -> 15 | IO a 16 | execute extract m = \case 17 | Match index item -> 18 | pure (m extract (Entry item (fromIntegral index) False)) 19 | Initial items -> 20 | initPar extract m items 21 | Refine entries -> 22 | refinePar extract m entries 23 | 24 | interpretFilter :: 25 | Member (Embed IO) r => 26 | InterpreterFor MenuFilter r 27 | interpretFilter = 28 | interpret \case 29 | MenuFilter mode query job -> 30 | embed (execute (MenuMode.extract mode) (MenuMode.matcher mode query) job) 31 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Final.hs: -------------------------------------------------------------------------------- 1 | {-# options_haddock prune, hide #-} 2 | 3 | -- |Combinators for using 'Final', used internally. 4 | module Ribosome.Final where 5 | 6 | import Polysemy.Final (withWeavingToFinal) 7 | 8 | inFinal :: 9 | ∀ r a . 10 | Member (Final IO) r => 11 | (∀ f . Functor f => f () -> (∀ x . Sem r x -> IO (f x)) -> (∀ x . x -> IO (f x)) -> (∀ x . f x -> Maybe x) -> IO (f a)) -> 12 | Sem r a 13 | inFinal f = 14 | withWeavingToFinal \ s wv ex -> 15 | f s (\ ma -> wv (ma <$ s)) (\ a -> pure (a <$ s)) ex 16 | 17 | inFinal_ :: 18 | ∀ r a . 19 | Member (Final IO) r => 20 | (∀ f . Functor f => (∀ x . Sem r x -> IO (Maybe x)) -> (Sem r () -> IO ()) -> (∀ x . x -> IO (f x)) -> IO (f a)) -> 21 | Sem r a 22 | inFinal_ f = 23 | withWeavingToFinal \ s wv ex -> 24 | let 25 | lowerMaybe :: ∀ x . Sem r x -> IO (Maybe x) 26 | lowerMaybe sem = 27 | ex <$> wv (sem <$ s) 28 | lowerUnit :: ∀ x . Sem r x -> IO () 29 | lowerUnit = 30 | fmap (fromMaybe ()) . lowerMaybe . void 31 | in f lowerMaybe lowerUnit (\ a -> pure (a <$ s)) 32 | -------------------------------------------------------------------------------- /packages/test/lib/Ribosome/Test/Ui.hs: -------------------------------------------------------------------------------- 1 | -- |Assertions for Neovim UI elements 2 | module Ribosome.Test.Ui where 3 | 4 | import Polysemy.Test (Hedgehog, assertEq, (===)) 5 | 6 | import Ribosome.Api.Window (currentCursor, cursor) 7 | import Ribosome.Host.Api.Data (Window) 8 | import Ribosome.Host.Api.Data (nvimListWins) 9 | import Ribosome.Host.Effect.Rpc (Rpc) 10 | 11 | -- |Assert the number of windows. 12 | windowCountIs :: 13 | Monad m => 14 | Members [Rpc, Hedgehog m] r => 15 | Int -> 16 | Sem r () 17 | windowCountIs count = do 18 | wins <- nvimListWins 19 | count === length wins 20 | 21 | -- |Assert the cursor position in a window. 22 | cursorIs :: 23 | Monad m => 24 | Members [Rpc, Hedgehog m] r => 25 | Int -> 26 | Int -> 27 | Window -> 28 | Sem r () 29 | cursorIs line col = 30 | assertEq (line, col) <=< cursor 31 | 32 | -- |Assert the cursor position in the current window. 33 | currentCursorIs :: 34 | Monad m => 35 | Members [Rpc, Hedgehog m] r => 36 | Int -> 37 | Int -> 38 | Sem r () 39 | currentCursorIs line col = 40 | assertEq (line, col) =<< currentCursor 41 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/PersistPathError.hs: -------------------------------------------------------------------------------- 1 | -- |Error for 'Ribosome.PersistPath'. 2 | module Ribosome.Data.PersistPathError where 3 | 4 | import Exon (exon) 5 | import Path (Abs, Dir, Path) 6 | import Polysemy.Log (Severity (Error)) 7 | 8 | import Ribosome.Host.Data.Report (Report (Report), Reportable (toReport)) 9 | import Ribosome.Host.Path (pathText) 10 | 11 | -- |The errors emitted by the effect 'Ribosome.PersistPath'. 12 | data PersistPathError = 13 | -- |Cannot determine the cache directory. 14 | Undefined 15 | | 16 | -- |General permissions error. 17 | Permissions (Path Abs Dir) 18 | deriving stock (Eq, Show) 19 | 20 | instance Reportable PersistPathError where 21 | toReport = \case 22 | Undefined -> 23 | Report msg ["PersistPathError.Undefined"] Error 24 | where 25 | msg = 26 | "g:ribosome_persistence_dir unset and XDG not available." 27 | Permissions (pathText -> path) -> 28 | Report msg ["PersistPathError.Permissions:", path] Error 29 | where 30 | msg = 31 | [exon|Couldn't create persistence dir '#{path}'|] 32 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Interpreter/Process/Embed.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Interpreter.Process.Embed where 2 | 3 | import Data.Serialize (Serialize) 4 | import Polysemy.Process (Process, ProcessOptions, withProcess_) 5 | import System.Process.Typed (ProcessConfig, proc) 6 | 7 | import Ribosome.Host.Data.BootError (BootError (BootError)) 8 | import Ribosome.Host.Interpreter.Process.Cereal (interpretProcessCerealNative) 9 | 10 | nvimArgs :: [String] 11 | nvimArgs = 12 | ["--embed", "-n", "-u", "NONE", "-i", "NONE", "--clean", "--headless"] 13 | 14 | nvimProc :: ProcessConfig () () () 15 | nvimProc = 16 | proc "nvim" nvimArgs 17 | 18 | interpretProcessCerealNvimEmbed :: 19 | Serialize a => 20 | Members [Error BootError, Log, Resource, Race, Async, Embed IO] r => 21 | Maybe ProcessOptions -> 22 | Maybe (ProcessConfig () () ()) -> 23 | InterpreterFor (Process a (Either Text a)) r 24 | interpretProcessCerealNvimEmbed options conf = 25 | interpretProcessCerealNative (fromMaybe def options) (fromMaybe nvimProc conf) . 26 | resumeHoistError (BootError . show) . 27 | withProcess_ . 28 | raiseUnder2 29 | -------------------------------------------------------------------------------- /packages/app/test/fixtures/new-project/action.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'latest-release' 3 | 4 | on: 5 | push: 6 | branches: 7 | - 'main' 8 | 9 | jobs: 10 | release: 11 | name: 'Release' 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2.4.0 15 | - uses: cachix/install-nix-action@v20 16 | with: 17 | extra_nix_config: | 18 | access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} 19 | extra-substituters = https://cach.cachix.org 20 | extra-trusted-public-keys = 12345 21 | - uses: cachix/cachix-action@v10 22 | with: 23 | name: cach 24 | signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' 25 | - name: 'build' 26 | run: | 27 | cp $(nix run .#appimage) test-project 28 | - uses: 'marvinpinto/action-automatic-releases@latest' 29 | name: 'create release' 30 | with: 31 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 32 | automatic_release_tag: 'latest' 33 | prerelease: true 34 | files: | 35 | test-project 36 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/Bang.hs: -------------------------------------------------------------------------------- 1 | -- |Special command parameter that activates the bang modifier. 2 | module Ribosome.Host.Data.Bang where 3 | 4 | import Data.MessagePack (Object (ObjectBool)) 5 | import Exon (exon) 6 | 7 | import Ribosome.Host.Class.Msgpack.Decode (pattern Msgpack, MsgpackDecode (fromMsgpack)) 8 | import Ribosome.Host.Class.Msgpack.Error (decodeError) 9 | 10 | -- |When this type is used as a parameter of a command handler function, the command is declared with the @-bang@ 11 | -- option, and when invoked, the argument passed to the handler is v'Bang' if the user specified the @!@ and 'NoBang' 12 | -- otherwise. 13 | data Bang = 14 | -- |Bang was used. 15 | Bang 16 | | 17 | -- |Bang was not used. 18 | NoBang 19 | deriving stock (Eq, Show) 20 | 21 | instance MsgpackDecode Bang where 22 | fromMsgpack = \case 23 | ObjectBool True -> 24 | Right Bang 25 | ObjectBool False -> 26 | Right NoBang 27 | Msgpack (1 :: Int) -> 28 | Right Bang 29 | Msgpack (0 :: Int) -> 30 | Right NoBang 31 | o -> 32 | decodeError [exon|Bang arg must be boolean: #{show o}|] 33 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Api/Position.hs: -------------------------------------------------------------------------------- 1 | -- |API functions for @getpos@. 2 | module Ribosome.Api.Position where 3 | 4 | import Ribosome.Host.Api.Data (nvimCallFunction) 5 | import Ribosome.Host.Class.Msgpack.Encode (toMsgpack) 6 | import Ribosome.Host.Data.RpcCall (RpcCall) 7 | import qualified Ribosome.Host.Effect.Rpc as Rpc 8 | import Ribosome.Host.Effect.Rpc (Rpc) 9 | 10 | -- |'RpcCall' for the function @getpos@ that returns a 4-tuple. 11 | getposCall :: 12 | Text -> 13 | RpcCall (Int, Int, Int, Int) 14 | getposCall expr = 15 | nvimCallFunction "getpos" [toMsgpack expr] 16 | 17 | -- |Call the function @getpos@ and return a 4-tuple. 18 | getpos :: 19 | Member Rpc r => 20 | Text -> 21 | Sem r (Int, Int, Int, Int) 22 | getpos = 23 | Rpc.sync . getposCall 24 | 25 | -- |Return the start and end coordinates of visual mode. 26 | visualPos :: 27 | Member Rpc r => 28 | Sem r ((Int, Int), (Int, Int)) 29 | visualPos = do 30 | ((_, lnumStart, colStart, _), (_, lnumEnd, colEnd, _)) <- Rpc.sync do 31 | start <- getposCall "'<" 32 | end <- getposCall "'>" 33 | pure (start, end) 34 | pure ((lnumStart, colStart), (lnumEnd, colEnd)) 35 | -------------------------------------------------------------------------------- /packages/host/test/Ribosome/Host/Test/EventTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Test.EventTest where 2 | 3 | import Conc (interpretSync, withAsync_) 4 | import qualified Polysemy.Conc.Sync as Sync 5 | import Polysemy.Test (UnitTest, assertJust) 6 | import Polysemy.Time (Seconds (Seconds)) 7 | 8 | import qualified Ribosome.Host.Api.Data as Data 9 | import Ribosome.Host.Class.Msgpack.Encode (toMsgpack) 10 | import Ribosome.Host.Data.Event (Event (Event)) 11 | import qualified Ribosome.Host.Effect.Rpc as Rpc 12 | import Ribosome.Host.Embed (embedNvim_) 13 | import Ribosome.Host.Unit.Run (runTest) 14 | 15 | listenEvent :: 16 | Members [EventConsumer Event, Sync Event] r => 17 | Sem r () 18 | listenEvent = 19 | subscribe do 20 | void . Sync.putWait (Seconds 5) =<< consume @Event 21 | 22 | target :: Event 23 | target = 24 | Event "nvim_error_event" [toMsgpack (0 :: Int), toMsgpack ("Vim(write):E32: No file name" :: Text)] 25 | 26 | test_errorEvent :: UnitTest 27 | test_errorEvent = 28 | runTest $ interpretSync $ embedNvim_ do 29 | withAsync_ listenEvent do 30 | Rpc.notify (Data.nvimCommand "write") 31 | assertJust target =<< Sync.wait (Seconds 5) 32 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Effect/VariableWatcher.hs: -------------------------------------------------------------------------------- 1 | -- |An effect used internally to execute handlers when Neovim variables are changed. 2 | module Ribosome.Effect.VariableWatcher where 3 | 4 | -- |The name of a variable that Ribosome should watch for changes. 5 | newtype WatchedVariable = 6 | WatchedVariable { unWatchedVariable :: Text } 7 | deriving stock (Eq, Show) 8 | deriving newtype (IsString, Ord) 9 | 10 | -- |An effect used internally to execute handlers when Neovim variables are changed. 11 | data VariableWatcher :: Effect where 12 | -- |Called when the internal logic determines that variables should be examined for updates. 13 | Update :: VariableWatcher m () 14 | -- |Stop running update handlers for the given variable. 15 | Unwatch :: WatchedVariable -> VariableWatcher m () 16 | 17 | makeSem_ ''VariableWatcher 18 | 19 | -- |Called when the internal logic determines that variables should be examined for updates. 20 | update :: 21 | Member VariableWatcher r => 22 | Sem r () 23 | 24 | -- |Stop running update handlers for the given variable. 25 | unwatch :: 26 | Member VariableWatcher r => 27 | WatchedVariable -> 28 | Sem r () 29 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/State.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.State where 2 | 3 | import qualified Data.Map.Strict as Map 4 | import Data.Trie (Trie) 5 | 6 | import Ribosome.Menu.Data.Entry (Entries) 7 | import Ribosome.Menu.Data.Filter (Filter) 8 | import Ribosome.Menu.Data.MenuItem (Items) 9 | import Ribosome.Menu.Data.MenuQuery (MenuQuery) 10 | 11 | data Primary i = 12 | Primary { 13 | items :: Items i, 14 | entries :: Entries i, 15 | query :: MenuQuery 16 | } 17 | deriving stock (Eq, Show, Generic) 18 | 19 | data Core i = 20 | Core { 21 | primary :: Primary i, 22 | itemCount :: Word, 23 | entryCount :: Word 24 | } 25 | deriving stock (Eq, Show, Generic) 26 | 27 | data Modal mode i = 28 | Modal { 29 | core :: Core i, 30 | history :: Map mode (Trie (Entries i)), 31 | mode :: mode 32 | } 33 | deriving stock (Eq, Show, Generic) 34 | 35 | type ModalState i = Modal Filter i 36 | 37 | modal :: 38 | ∀ i mode . 39 | mode -> 40 | Modal mode i 41 | modal = 42 | Modal (Core (Primary mempty mempty mempty) 0 0) Map.empty 43 | 44 | instance Default mode => Default (Modal mode i) where 45 | def = modal def 46 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/Syntax/SyntaxKind.hs: -------------------------------------------------------------------------------- 1 | -- |Data types for syntax keyword, match, region and verbatim code. 2 | module Ribosome.Data.Syntax.SyntaxKind where 3 | 4 | -- |A syntax region, defined by patterns for start and end, and optionally a skip pattern. 5 | -- 6 | -- Offsets define precisely where highlighting begins. 7 | -- 8 | -- See @:help :syn-region@. 9 | data SyntaxRegion = 10 | SyntaxRegion { 11 | start :: Text, 12 | end :: Text, 13 | skip :: Maybe Text, 14 | startOffset :: Maybe Text, 15 | endOffset :: Maybe Text 16 | } 17 | deriving stock (Eq, Show, Generic) 18 | 19 | -- |Data type for syntax keyword, match, region and verbatim code. 20 | data SyntaxKind = 21 | -- |A list of precise tokens that should be matched. 22 | Keyword { keywords :: NonEmpty Text } 23 | | 24 | -- |A single pattern. 25 | Match { pattern_ :: Text } 26 | | 27 | -- |A region with start and end patterns. 28 | Region SyntaxRegion 29 | | 30 | -- |A command that is not processed by the DSL and inserted verbatim. 31 | -- 32 | -- Useful for things like @syntax sync@. 33 | Verbatim { command :: Text } 34 | deriving stock (Eq, Show) 35 | -------------------------------------------------------------------------------- /packages/menu/test/Ribosome/Menu/Test/NoMatchTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Test.NoMatchTest where 2 | 3 | import Polysemy.Test (UnitTest) 4 | 5 | import Ribosome.Menu.Data.Filter (substring) 6 | import Ribosome.Menu.Data.State (modal) 7 | import Ribosome.Menu.Effect.MenuTest (quit, setPromptWait) 8 | import Ribosome.Menu.Scratch (menuScratchSized) 9 | import Ribosome.Menu.Test.Menu (awaitItemsBuffer) 10 | import Ribosome.Menu.Test.Run (testStaticNvimMenuSimple) 11 | import Ribosome.Test.Embed (testEmbed_) 12 | import Ribosome.Test.Error (testError) 13 | 14 | itemsNoMatch :: [NonEmpty Text] 15 | itemsNoMatch = 16 | [["item 1"], ["item 2"], ["item 3"], ["item 4"]] 17 | 18 | initialBuffer :: [Text] 19 | initialBuffer = 20 | [ 21 | "item 4", 22 | "item 3", 23 | "item 2", 24 | "item 1" 25 | ] 26 | 27 | test_filterNoMatch :: UnitTest 28 | test_filterNoMatch = 29 | testEmbed_ do 30 | testError $ testStaticNvimMenuSimple itemsNoMatch def (modal substring) (menuScratchSized 4) mempty do 31 | awaitItemsBuffer initialBuffer 32 | setPromptWait "3" 33 | awaitItemsBuffer ["item 3"] 34 | setPromptWait "3x" 35 | awaitItemsBuffer [""] 36 | quit 37 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/WindowView.hs: -------------------------------------------------------------------------------- 1 | -- |Codec data type for @winsaveview@. 2 | module Ribosome.Data.WindowView where 3 | 4 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode) 5 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode) 6 | 7 | -- |Codec data type for @winsaveview@. 8 | data WindowView = 9 | WindowView { 10 | lnum :: Int, 11 | topline :: Int 12 | } 13 | deriving stock (Eq, Show, Generic) 14 | deriving anyclass (MsgpackEncode, MsgpackDecode) 15 | 16 | -- |Codec data type for @winrestview@. 17 | data PartialWindowView = 18 | PartialWindowView { 19 | lnum :: Maybe Int, 20 | topline :: Maybe Int 21 | } 22 | deriving stock (Eq, Show, Generic) 23 | deriving anyclass (MsgpackEncode, MsgpackDecode) 24 | 25 | -- |Convert between the return type of @winsaveview@ and the parameter type of @winrestview@. 26 | class AsPartialWindowView a where 27 | asPartialWindowView :: a -> PartialWindowView 28 | 29 | instance AsPartialWindowView WindowView where 30 | asPartialWindowView (WindowView l t) = 31 | PartialWindowView (Just l) (Just t) 32 | 33 | instance AsPartialWindowView PartialWindowView where 34 | asPartialWindowView = 35 | id 36 | -------------------------------------------------------------------------------- /packages/host/test/Ribosome/Host/Test/AsyncTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Test.AsyncTest where 2 | 3 | import Conc (interpretSync) 4 | import qualified Polysemy.Conc.Sync as Sync 5 | import Polysemy.Test (UnitTest, assertJust) 6 | import Polysemy.Time (Seconds (Seconds)) 7 | 8 | import Ribosome.Host.Api.Data (nvimCallFunction) 9 | import Ribosome.Host.Class.Msgpack.Encode (toMsgpack) 10 | import Ribosome.Host.Data.Execution (Execution (Async)) 11 | import Ribosome.Host.Data.RpcHandler (Handler, RpcHandler) 12 | import qualified Ribosome.Host.Effect.Rpc as Rpc 13 | import Ribosome.Host.Embed (embedNvim) 14 | import Ribosome.Host.Handler (rpcFunction) 15 | import Ribosome.Host.Unit.Run (runTest) 16 | 17 | hand :: 18 | Member (Sync Int) r => 19 | Int -> 20 | Handler r () 21 | hand = 22 | void . Sync.putWait (Seconds 5) 23 | 24 | handlers :: 25 | ∀ r . 26 | Member (Sync Int) r => 27 | [RpcHandler r] 28 | handlers = 29 | [ 30 | rpcFunction "Fun" Async hand 31 | ] 32 | 33 | test_async :: UnitTest 34 | test_async = 35 | runTest $ interpretSync $ embedNvim handlers do 36 | Rpc.notify (nvimCallFunction @() "Fun" [toMsgpack (47 :: Int)]) 37 | assertJust 47 =<< Sync.takeWait (Seconds 1) 38 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/PersistError.hs: -------------------------------------------------------------------------------- 1 | -- |Error for 'Ribosome.Persist'. 2 | module Ribosome.Data.PersistError where 3 | 4 | import Exon (exon) 5 | import Polysemy.Log (Severity (Error)) 6 | 7 | import Ribosome.Data.PersistPathError (PersistPathError) 8 | import Ribosome.Host.Data.Report (Report (Report), Reportable (toReport)) 9 | 10 | -- |The errors emitted by the effect 'Ribosome.PersistPath'. 11 | data PersistError = 12 | -- |Can't access the persistence files. 13 | Permission Text 14 | | 15 | -- |Data in the persistence file has invalid format. 16 | Decode Text Text 17 | | 18 | -- |'Ribosome.PeristPath' threw an error. 19 | Path PersistPathError 20 | deriving stock (Eq, Show) 21 | 22 | instance Reportable PersistError where 23 | toReport = \case 24 | Permission path -> 25 | Report msg ["PersistError.Permission:", path] Error 26 | where 27 | msg = 28 | [exon|Insufficient permissions for persistence file: #{path}|] 29 | Decode path err -> 30 | Report msg ["PersistError.Decode:", path, err] Error 31 | where 32 | msg = 33 | [exon|invalid data in persistence file, please delete it: #{path}|] 34 | Path err -> 35 | toReport err 36 | -------------------------------------------------------------------------------- /packages/ribosome/test/Ribosome/Test/Error.hs: -------------------------------------------------------------------------------- 1 | -- |Combinators for aborting tests on Ribosome errors 2 | module Ribosome.Test.Error where 3 | 4 | import Polysemy.Test (TestError (TestError)) 5 | 6 | import Ribosome.Host.Data.Report (Reportable, mapReport, reportMessages) 7 | import Ribosome.Host.Data.RpcHandler (Handler) 8 | 9 | -- |Convert the effect @eff@ to @eff '!!' err@ and @'Error' 'TestError'@, failing the test when 'Stop' is used. 10 | resumeTestError :: 11 | ∀ eff e r . 12 | Show e => 13 | Members [eff !! e, Error TestError] r => 14 | InterpreterFor eff r 15 | resumeTestError = 16 | resumeHoistError (TestError . show) 17 | 18 | -- |Convert a 'LogReport' from a 'Handler' to an @'Error' 'TestError'@, failing the test when 'Stop' is used. 19 | testHandler :: 20 | Member (Error TestError) r => 21 | Handler r a -> 22 | Sem r a 23 | testHandler = 24 | stopToError . mapStop (TestError . reportMessages) . raiseUnder 25 | 26 | -- |Reinterpret @'Stop' err@ to @'Error' 'TestError'@ if @err@ is an instance of 'Reportable'. 27 | testError :: 28 | ∀ e r a . 29 | Reportable e => 30 | Member (Error TestError) r => 31 | Sem (Stop e : r) a -> 32 | Sem r a 33 | testError = 34 | testHandler . mapReport . raiseUnder 35 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Msgpack.hs: -------------------------------------------------------------------------------- 1 | -- |Tools for writing instances of 'MsgpackDecode'. 2 | module Ribosome.Msgpack ( 3 | module Ribosome.Host.Class.Msgpack.Decode, 4 | module Ribosome.Host.Class.Msgpack.Encode, 5 | module Ribosome.Host.Class.Msgpack.Error, 6 | module Ribosome.Host.Class.Msgpack.Util, 7 | module Ribosome.Host.Class.Msgpack.Map, 8 | module Ribosome.Host.Class.Msgpack.Array, 9 | ) where 10 | 11 | import Ribosome.Host.Class.Msgpack.Array (msgpackArray) 12 | import Ribosome.Host.Class.Msgpack.Decode (MissingKey (..), pattern Msgpack, MsgpackDecode (..), fromMsgpackGeneric) 13 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode (..), toMsgpackGeneric) 14 | import Ribosome.Host.Class.Msgpack.Error ( 15 | DecodeError (..), 16 | FieldError (..), 17 | decodeError, 18 | decodeIncompatible, 19 | incompatible, 20 | renderError, 21 | toDecodeError, 22 | ) 23 | import Ribosome.Host.Class.Msgpack.Map (msgpackMap) 24 | import Ribosome.Host.Class.Msgpack.Util ( 25 | pattern MsgpackString, 26 | byteStringField, 27 | decodeByteString, 28 | decodeFractional, 29 | decodeIntegral, 30 | decodeString, 31 | decodeUtf8Lenient, 32 | fractionalField, 33 | integralField, 34 | readField, 35 | stringField, 36 | ) 37 | -------------------------------------------------------------------------------- /packages/ribosome/test/Ribosome/Test/WatcherTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Test.WatcherTest where 2 | 3 | import Conc (interpretAtomic) 4 | import Data.MessagePack (Object) 5 | import Polysemy.Test (UnitTest, (===)) 6 | 7 | import Ribosome.Api.Autocmd (doautocmd) 8 | import Ribosome.Host.Api.Data (nvimSetVar) 9 | import Ribosome.Host.Data.Report (Report) 10 | import Ribosome.Host.Data.RpcError (RpcError) 11 | import Ribosome.Host.Effect.Rpc (Rpc) 12 | import Ribosome.Test.Wait (assertWait) 13 | import Ribosome.Unit.Run (runTest, testHandlers) 14 | 15 | changed :: 16 | Members [AtomicState Int, Rpc !! RpcError, Stop Report] r => 17 | Object -> 18 | Sem r () 19 | changed _ = 20 | atomicModify' (+ 1) 21 | 22 | test_varWatcher :: UnitTest 23 | test_varWatcher = 24 | runTest $ interpretAtomic 0 $ testHandlers mempty [("trigger", changed)] do 25 | nvimSetVar "trigger" (4 :: Int) 26 | doautocmd "CmdlineLeave" 27 | assertWait atomicGet ((1 :: Int) ===) 28 | nvimSetVar "trigger" (5 :: Int) 29 | doautocmd "CmdlineLeave" 30 | doautocmd "CmdlineLeave" 31 | doautocmd "CmdlineLeave" 32 | assertWait atomicGet ((2 :: Int) ===) 33 | nvimSetVar "trigger" (6 :: Int) 34 | doautocmd "CmdlineLeave" 35 | assertWait atomicGet ((3 :: Int) ===) 36 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "hix": { 4 | "inputs": { 5 | "nixpkgs": "nixpkgs" 6 | }, 7 | "locked": { 8 | "lastModified": 1715260690, 9 | "narHash": "sha256-/emPn6oRuHCdEMvcRNHDKP1cE7ugqp3AKhyNXo7alHM=", 10 | "ref": "refs/heads/main", 11 | "rev": "5c57f860c631023c52203c9e75dae87bda5cad6e", 12 | "revCount": 707, 13 | "type": "git", 14 | "url": "https://git.tryp.io/tek/hix" 15 | }, 16 | "original": { 17 | "type": "git", 18 | "url": "https://git.tryp.io/tek/hix" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1699948764, 24 | "narHash": "sha256-etefGjZwNF58NmFusFI/4i645HB4ymODXTvWRKOYBA4=", 25 | "owner": "nixos", 26 | "repo": "nixpkgs", 27 | "rev": "4f2bd72692b1aae0a6db485ed3a83d1a933c2cac", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "nixos", 32 | "repo": "nixpkgs", 33 | "rev": "4f2bd72692b1aae0a6db485ed3a83d1a933c2cac", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "hix": "hix" 40 | } 41 | } 42 | }, 43 | "root": "root", 44 | "version": 7 45 | } 46 | -------------------------------------------------------------------------------- /packages/host/test/Ribosome/Host/Test/CommandParamErrorTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Test.CommandParamErrorTest where 2 | 3 | import Control.DeepSeq (force) 4 | import Control.Exception (evaluate) 5 | import qualified Data.Text as Text 6 | import Polysemy.Test (Hedgehog, UnitTest, assertLeft, runTestAuto) 7 | 8 | import Ribosome.Host.Test.CommandParamErrorDecls (argAfterArgs, argsAfterArg) 9 | import Data.MessagePack (Object) 10 | 11 | typeError :: 12 | Members [Hedgehog IO, Embed IO] r => 13 | [Text] -> 14 | (Map Text Object, [Text]) -> 15 | Sem r () 16 | typeError msg t = do 17 | e <- tryAny (evaluate (force t)) 18 | assertLeft msg (first trunc e) 19 | where 20 | trunc = 21 | fmap Text.strip . take (length msg) . drop 1 . lines 22 | 23 | argAfterArgsError :: [Text] 24 | argAfterArgsError = 25 | [ 26 | "• Custom parameter types (here Int) cannot be combined with Args", 27 | "since Args consumes all arguments" 28 | ] 29 | 30 | argsAfterArgError :: [Text] 31 | argsAfterArgError = 32 | [ 33 | "• Command option type Bang may not come after non-option" 34 | ] 35 | 36 | test_paramError :: UnitTest 37 | test_paramError = 38 | runTestAuto do 39 | typeError argAfterArgsError argAfterArgs 40 | typeError argsAfterArgError argsAfterArg 41 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Stream/Util.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Stream.Util where 2 | 3 | import Data.Maybe (fromJust) 4 | import qualified Queue 5 | import qualified Streamly.Prelude as Stream 6 | import Streamly.Prelude (IsStream) 7 | 8 | import Ribosome.Final (inFinal_) 9 | 10 | takeUntilNothing :: 11 | Monad m => 12 | IsStream t => 13 | Functor (t m) => 14 | t m (Maybe a) -> 15 | t m a 16 | takeUntilNothing s = 17 | fromJust <$> Stream.takeWhile isJust s 18 | 19 | repeatUntilNothing :: 20 | ∀ t m a . 21 | Monad m => 22 | Monad (t m) => 23 | IsStream t => 24 | m (Maybe a) -> 25 | t m a 26 | repeatUntilNothing ma = 27 | spin 28 | where 29 | spin = 30 | Stream.fromEffect ma >>= \case 31 | Just a -> Stream.cons a spin 32 | Nothing -> Stream.nil 33 | 34 | nothingTerminated :: 35 | Monad m => 36 | IsStream t => 37 | t m a -> 38 | t m (Maybe a) 39 | nothingTerminated s = 40 | Stream.serial (Just <$> s) (Stream.fromPure Nothing) 41 | 42 | queueStream :: 43 | IsStream t => 44 | Functor (t IO) => 45 | Members [Queue a, Final IO] r => 46 | Sem r (t IO a) 47 | queueStream = 48 | inFinal_ \ lowerMaybe _ pureF -> 49 | pureF do 50 | takeUntilNothing (Stream.repeatM (join <$> lowerMaybe Queue.readMaybe)) 51 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Class/MenuMode.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Class.MenuMode where 2 | 3 | import Ribosome.Menu.Data.Entry (Entry) 4 | import qualified Ribosome.Menu.Data.MenuItem 5 | import Ribosome.Menu.Data.MenuItem (MenuItem) 6 | import Ribosome.Menu.Data.MenuQuery (MenuQuery) 7 | 8 | type Extractor i a = 9 | MenuItem i -> Maybe a 10 | 11 | type Matcher i a = 12 | Extractor i a -> Entry i -> Maybe (Int, Entry i) 13 | 14 | class ( 15 | Show mode, 16 | Ord mode 17 | ) => MenuMode (i :: Type) (mode :: Type) where 18 | type MatchOn mode :: Type 19 | type MatchOn _ = Text 20 | 21 | cycleFilter :: mode -> mode 22 | cycleFilter = id 23 | 24 | renderFilter :: mode -> Text 25 | renderFilter _ = "no mode" 26 | 27 | renderExtra :: mode -> Int -> Maybe Text 28 | renderExtra _ _ = Nothing 29 | 30 | matcher :: mode -> MenuQuery -> Matcher i (MatchOn mode) 31 | 32 | extract :: mode -> MenuItem i -> Maybe (MatchOn mode) 33 | default extract :: MatchOn mode ~ Text => mode -> MenuItem i -> Maybe (MatchOn mode) 34 | extract _ i = Just i.text 35 | 36 | type ExtractFor mode i = Extractor i (MatchOn mode) 37 | 38 | type MatcherFor mode i = Matcher i (MatchOn mode) 39 | 40 | instance MenuMode i () where 41 | matcher () _ _ e = Just (0, e) 42 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/MenuItem.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.MenuItem ( 2 | module Ribosome.Menu.Data.MenuItem, 3 | MenuItem (MenuItem, ..), 4 | ) where 5 | 6 | import qualified Data.IntMap.Strict as IntMap 7 | import qualified Data.Text as Text 8 | 9 | data MenuItem a = 10 | UnsafeMenuItem { 11 | meta :: a, 12 | text :: Text, 13 | lines :: Word, 14 | render :: NonEmpty Text 15 | } 16 | deriving stock (Eq, Show, Ord, Functor, Generic) 17 | 18 | pattern MenuItem :: a -> Text -> NonEmpty Text -> MenuItem a 19 | pattern MenuItem {meta, text, render} <- UnsafeMenuItem {..} 20 | where 21 | MenuItem meta text render = UnsafeMenuItem {lines = fromIntegral (length render), ..} 22 | 23 | {-# complete MenuItem #-} 24 | 25 | type Items a = 26 | IntMap (MenuItem a) 27 | 28 | simpleMenuItem :: a -> Text -> MenuItem a 29 | simpleMenuItem a t = 30 | MenuItem a t [t] 31 | 32 | simpleMenuItemLines :: a -> NonEmpty Text -> MenuItem a 33 | simpleMenuItemLines a t = 34 | MenuItem a (Text.intercalate "\n" (toList t)) t 35 | 36 | simpleItems :: [Text] -> Items () 37 | simpleItems = 38 | IntMap.fromList . zip [0..] . fmap (simpleMenuItem ()) 39 | 40 | intItems :: [Int] -> Items Int 41 | intItems nums = 42 | IntMap.fromList [(i, simpleMenuItem i (show i)) | i <- nums] 43 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/PluginConfig.hs: -------------------------------------------------------------------------------- 1 | -- |Global configuration for a Ribosome plugin. 2 | module Ribosome.Data.PluginConfig where 3 | 4 | import Exon (exon) 5 | import Options.Applicative (Parser) 6 | 7 | import Ribosome.Data.PluginName (PluginName) 8 | import Ribosome.Host.Data.HostConfig (HostConfig) 9 | 10 | -- |The full configuration for a Ribosome plugin, consisting of the 'HostConfig', the plugin's name, and an arbitrary 11 | -- type for additional config defined by individual plugins. 12 | data PluginConfig c = 13 | PluginConfig { 14 | name :: PluginName, 15 | host :: HostConfig, 16 | custom :: Parser c 17 | } 18 | deriving stock (Generic) 19 | 20 | instance Show (PluginConfig c) where 21 | showsPrec d PluginConfig {..} = 22 | showParen (d > 10) [exon|PluginConfing { name = #{showsPrec 11 name}, host = #{showsPrec 11 host} }|] 23 | 24 | instance Eq (PluginConfig c) where 25 | PluginConfig ln lh _ == PluginConfig rn rh _ = 26 | ln == rn && lh == rh 27 | 28 | -- |Construct a simple 'PluginConfig' with the default config for the host, given the plugin's name. 29 | pluginNamed :: PluginName -> PluginConfig () 30 | pluginNamed name = 31 | PluginConfig name def unit 32 | 33 | instance IsString (PluginConfig ()) where 34 | fromString = 35 | pluginNamed . fromString 36 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/WindowConfig.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.WindowConfig where 2 | 3 | import Ribosome.Data.Mapping (MappingSpec) 4 | import Ribosome.Data.ScratchOptions (ScratchOptions) 5 | import Ribosome.Menu.Prompt.Data.Prompt (PromptState) 6 | 7 | data WindowOptions = 8 | WindowOptions { 9 | prompt :: PromptState, 10 | items :: ScratchOptions, 11 | status :: Maybe ScratchOptions, 12 | builtinHandlers :: Bool, 13 | defaultHandlers :: Bool 14 | } 15 | deriving stock (Eq, Show, Generic) 16 | 17 | instance Default WindowOptions where 18 | def = 19 | WindowOptions { 20 | prompt = def, 21 | items = def, 22 | status = Just def, 23 | builtinHandlers = True, 24 | defaultHandlers = True 25 | } 26 | 27 | data WindowConfig = 28 | WindowConfig { 29 | items :: ScratchOptions, 30 | status :: Maybe ScratchOptions, 31 | mappings :: [MappingSpec] 32 | } 33 | deriving stock (Eq, Show, Generic) 34 | 35 | instance Default WindowConfig where 36 | def = 37 | WindowConfig { 38 | items = def, 39 | status = Just def, 40 | mappings = mempty 41 | } 42 | 43 | toWindowConfig :: 44 | WindowOptions -> 45 | [MappingSpec] -> 46 | WindowConfig 47 | toWindowConfig WindowOptions {..} mappings = 48 | WindowConfig {..} 49 | -------------------------------------------------------------------------------- /packages/app/lib/Ribosome/App/Templates/PluginHs.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.App.Templates.PluginHs where 2 | 3 | import Exon (exon) 4 | 5 | import Ribosome.App.Data (ModuleName (ModuleName), ProjectName (ProjectName)) 6 | 7 | pluginHs :: ProjectName -> ModuleName -> Text 8 | pluginHs (ProjectName name) (ModuleName modName) = 9 | [exon|module #{modName}.Plugin where 10 | 11 | import Conc (interpretAtomic, withAsync_) 12 | import Ribosome ( 13 | Execution (Sync), 14 | Handler, 15 | RpcHandler, 16 | rpcFunction, 17 | runNvimPluginIO, 18 | ) 19 | 20 | type #{modName}Stack = 21 | '[ 22 | AtomicState Int 23 | ] 24 | 25 | ping :: 26 | Member (AtomicState Int) r => 27 | Handler r Int 28 | ping = 29 | atomicState' \ s -> (s + 1, s + 1) 30 | 31 | handlers :: 32 | Member (AtomicState Int) r => 33 | [RpcHandler r] 34 | handlers = 35 | [ 36 | rpcFunction "#{modName}Ping" Sync ping 37 | ] 38 | 39 | prepare :: 40 | Sem r () 41 | prepare = 42 | unit 43 | 44 | interpret#{modName}Stack :: 45 | Members [Resource, Race, Async, Embed IO] r => 46 | InterpretersFor #{modName}Stack r 47 | interpret#{modName}Stack sem = 48 | interpretAtomic 0 do 49 | withAsync_ prepare sem 50 | 51 | main :: IO () 52 | main = 53 | runNvimPluginIO @#{modName}Stack "#{name}" interpret#{modName}Stack handlers 54 | |] 55 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/HostConfig.hs: -------------------------------------------------------------------------------- 1 | -- |The configuration for a Ribosome plugin host. 2 | module Ribosome.Host.Data.HostConfig where 3 | 4 | import Log (Severity (Crit, Info)) 5 | import Path (Abs, File, Path) 6 | 7 | -- |Logging config for a host, with different levels for Neovim echoing, stderr and file logs. 8 | -- 9 | -- /Note/ that stderr logging will be sent to Neovim when the plugin is running in remote mode, which will be ignored 10 | -- unless the plugin is started with a stderr handler. 11 | data LogConfig = 12 | LogConfig { 13 | logFile :: Maybe (Path Abs File), 14 | logLevelEcho :: Severity, 15 | logLevelStderr :: Severity, 16 | logLevelFile :: Severity, 17 | dataLogConc :: Bool 18 | } 19 | deriving stock (Eq, Show, Generic) 20 | 21 | instance Default LogConfig where 22 | def = 23 | LogConfig Nothing Info Crit Info True 24 | 25 | -- |The configuration for a host, which consists only of a 'LogConfig'. 26 | newtype HostConfig = 27 | HostConfig { 28 | hostLog :: LogConfig 29 | } 30 | deriving stock (Eq, Show, Generic) 31 | 32 | instance Default HostConfig where 33 | def = 34 | HostConfig def 35 | 36 | -- |Set the stderr level on a 'HostConfig'. 37 | setStderr :: Severity -> HostConfig -> HostConfig 38 | setStderr l c = 39 | c { hostLog = c.hostLog { logLevelStderr = l } } 40 | -------------------------------------------------------------------------------- /packages/host/test/Ribosome/Host/Test/CommandRegisterTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Test.CommandRegisterTest where 2 | 3 | import Conc (interpretAtomic) 4 | import Polysemy.Test (UnitTest, assertJust) 5 | 6 | import Ribosome.Host.Api.Data (nvimCommand, nvimGetVar, nvimSetVar) 7 | import Ribosome.Host.Data.CommandRegister (CommandRegister (CommandRegister)) 8 | import Ribosome.Host.Data.Execution (Execution (Sync)) 9 | import Ribosome.Host.Data.Report (resumeReport) 10 | import Ribosome.Host.Data.RpcError (RpcError) 11 | import Ribosome.Host.Data.RpcHandler (Handler, RpcHandler) 12 | import Ribosome.Host.Effect.Rpc (Rpc) 13 | import Ribosome.Host.Embed (embedNvim) 14 | import Ribosome.Host.Handler (rpcCommand) 15 | import Ribosome.Host.Unit.Run (runTest) 16 | 17 | var :: Text 18 | var = 19 | "test_var" 20 | 21 | reg :: 22 | Member (Rpc !! RpcError) r => 23 | CommandRegister -> 24 | Handler r () 25 | reg (CommandRegister r) = 26 | resumeReport (nvimSetVar var r) 27 | 28 | handlers :: 29 | ∀ r . 30 | Members [AtomicState Int, Rpc !! RpcError] r => 31 | [RpcHandler r] 32 | handlers = 33 | [ 34 | rpcCommand "Register" Sync reg 35 | ] 36 | 37 | test_register :: UnitTest 38 | test_register = 39 | runTest $ interpretAtomic 0 $ embedNvim handlers do 40 | nvimCommand "Register x" 41 | assertJust @Text "x" =<< nvimGetVar var 42 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/SettingError.hs: -------------------------------------------------------------------------------- 1 | -- |Error for 'Ribosome.Settings'. 2 | module Ribosome.Data.SettingError where 3 | 4 | import Exon (exon) 5 | import Log (Severity (Error)) 6 | 7 | import Ribosome.Host.Class.Msgpack.Error (DecodeError, renderError) 8 | import Ribosome.Host.Data.Report (Report (Report), Reportable (toReport)) 9 | import Ribosome.Host.Data.RpcError (RpcError, rpcError) 10 | 11 | -- |The errors emitted by the effect 'Ribosome.Settings'. 12 | data SettingError = 13 | -- |The variable is unset and has no associated default. 14 | Unset Text 15 | | 16 | -- |The variable contains data that is incompatible with the type parameter of the 'Ribosome.Setting'. 17 | Decode Text DecodeError 18 | | 19 | -- |Something went wrong while attempting to set a variable. 20 | UpdateFailed Text RpcError 21 | deriving stock (Eq, Show) 22 | 23 | instance Reportable SettingError where 24 | toReport = \case 25 | Unset key -> 26 | Report [exon|Mandatory setting '#{key}' is unset|] ["SettingError.Unset:", key] Error 27 | Decode key msg -> 28 | Report [exon|Setting '#{key}' has invalid value: #{renderError msg}|] ["SettingError.Decode:", key, show msg] Error 29 | UpdateFailed key err -> 30 | Report [exon|Failed to update setting '#{key}': #{rpcError err}|] ["SettingError.UpdateFailed:", key, show err] Error 31 | -------------------------------------------------------------------------------- /packages/host/test/Ribosome/Host/Test/CommandModsTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Test.CommandModsTest where 2 | 3 | import Conc (interpretAtomic) 4 | import Polysemy.Test (UnitTest, assertJust) 5 | 6 | import Ribosome.Host.Api.Data (nvimCommand, nvimGetVar, nvimSetVar) 7 | import Ribosome.Host.Data.CommandMods (CommandMods (CommandMods)) 8 | import Ribosome.Host.Data.Execution (Execution (Sync)) 9 | import Ribosome.Host.Data.Report (resumeReport) 10 | import Ribosome.Host.Data.RpcError (RpcError) 11 | import Ribosome.Host.Data.RpcHandler (Handler, RpcHandler) 12 | import Ribosome.Host.Effect.Rpc (Rpc) 13 | import Ribosome.Host.Embed (embedNvim) 14 | import Ribosome.Host.Handler (rpcCommand) 15 | import Ribosome.Host.Unit.Run (runTest) 16 | 17 | var :: Text 18 | var = 19 | "test_var" 20 | 21 | mods :: 22 | Member (Rpc !! RpcError) r => 23 | CommandMods -> 24 | Handler r () 25 | mods = \case 26 | CommandMods m -> 27 | resumeReport (nvimSetVar var m) 28 | 29 | handlers :: 30 | ∀ r . 31 | Members [AtomicState Int, Rpc !! RpcError] r => 32 | [RpcHandler r] 33 | handlers = 34 | [ 35 | rpcCommand "Mods" Sync mods 36 | ] 37 | 38 | test_mods :: UnitTest 39 | test_mods = 40 | runTest $ interpretAtomic 0 $ embedNvim handlers do 41 | nvimCommand "belowright silent lockmarks Mods" 42 | assertJust @Text "lockmarks silent belowright" =<< nvimGetVar var 43 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/ScratchState.hs: -------------------------------------------------------------------------------- 1 | -- |Scratch buffer state. 2 | module Ribosome.Data.ScratchState where 3 | 4 | import Ribosome.Data.ScratchId (ScratchId) 5 | import Ribosome.Data.ScratchOptions (ScratchOptions) 6 | import Ribosome.Host.Api.Data (Buffer, Tabpage, Window) 7 | import Ribosome.Host.Data.RpcType (AutocmdId) 8 | 9 | -- |The configuration and Neovim resources that define a scratch buffer and describe its previously recorded UI state. 10 | data ScratchState = 11 | ScratchState { 12 | -- |The scratch buffer's ID stored in the state. 13 | id :: ScratchId, 14 | -- |The configuration used to create the scratch buffer. 15 | options :: ScratchOptions, 16 | -- |The Neovim buffer handle that was returned when it was last updated. 17 | buffer :: Buffer, 18 | -- |The Neovim window handle that was returned when it was last updated. 19 | window :: Window, 20 | -- |The Neovim window handle that denotes the window that was active when the scratch buffer was created. 21 | previous :: Window, 22 | -- |The Neovim tabpage handle that was returned when it was last updated, if a tab was requested by the 23 | -- configuration. 24 | tab :: Maybe Tabpage, 25 | -- |The ID of the autocmd that fires when the user deletes the scratch buffer. 26 | autocmdId :: AutocmdId 27 | } 28 | deriving stock (Eq, Show, Generic) 29 | -------------------------------------------------------------------------------- /packages/app/lib/Ribosome/App/Cli.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.App.Cli where 2 | 3 | import Polysemy.Chronos (ChronosTime, interpretTimeChronos) 4 | import System.Exit (exitFailure) 5 | import System.IO (stderr) 6 | 7 | import Ribosome.App.Boot (generateBoot) 8 | import Ribosome.App.Data (Global (Global)) 9 | import Ribosome.App.Error (RainbowError, runRainbowErrorAnd) 10 | import Ribosome.App.NewOptions (newOptions) 11 | import Ribosome.App.NewProject (newProject) 12 | import Ribosome.App.Options (Command (Boot, New), GlobalOptions (GlobalOptions), Options (..), parseCli) 13 | import Ribosome.App.ProjectOptions (projectOptions) 14 | 15 | runCommand :: 16 | Members [ChronosTime, Stop RainbowError, Embed IO] r => 17 | Global -> 18 | Command -> 19 | Sem r () 20 | runCommand global = \case 21 | New opts -> do 22 | conf <- newOptions opts 23 | newProject global conf 24 | Boot opts -> do 25 | conf <- projectOptions False opts 26 | generateBoot global conf 27 | 28 | runOptions :: 29 | Members [ChronosTime, Stop RainbowError, Embed IO] r => 30 | Options -> 31 | Sem r () 32 | runOptions (Options (GlobalOptions quiet force) cmd) = 33 | runCommand (Global (fromMaybe False quiet) (fromMaybe False force)) cmd 34 | 35 | main :: IO () 36 | main = do 37 | conf <- parseCli 38 | runConc $ interpretTimeChronos (runRainbowErrorAnd stderr (embed exitFailure) (runOptions conf)) 39 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Effect/Scratch.hs: -------------------------------------------------------------------------------- 1 | {-# options_haddock prune #-} 2 | 3 | -- |Scratch buffers 4 | module Ribosome.Effect.Scratch where 5 | 6 | import Prelude hiding (find, get, show) 7 | 8 | import Ribosome.Data.ScratchId (ScratchId) 9 | import Ribosome.Data.ScratchOptions (ScratchOptions) 10 | import Ribosome.Data.ScratchState (ScratchState) 11 | 12 | -- |This effect manages scratch buffers, that is, transient buffers displaying text not associated with a file. 13 | -- See 'ScratchOptions' for configuration. 14 | data Scratch :: Effect where 15 | -- |Open a new scratch buffer and set its content to the supplied text. 16 | Show :: Foldable t => t Text -> ScratchOptions -> Scratch m ScratchState 17 | -- |Find a previously defined scratch buffer, ensure it is open and set its content to the supplied text. 18 | Update :: Foldable t => ScratchId -> t Text -> Scratch m ScratchState 19 | -- |Close a scratch buffer. 20 | Delete :: ScratchId -> Scratch m () 21 | -- |Return the state of all currently managed scratch buffers. 22 | Get :: Scratch m [ScratchState] 23 | -- |Look up a scratch buffer by its ID. 24 | Find :: ScratchId -> Scratch m (Maybe ScratchState) 25 | 26 | makeSem ''Scratch 27 | 28 | -- |Create an empty scratch buffer. 29 | open :: 30 | Member Scratch r => 31 | ScratchOptions -> 32 | Sem r ScratchState 33 | open = 34 | show @_ @[] mempty 35 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Api/Mode.hs: -------------------------------------------------------------------------------- 1 | -- |API functions for obtaining Neovim's current mode. 2 | module Ribosome.Api.Mode where 3 | 4 | import Ribosome.Data.Mode (NvimMode) 5 | import Ribosome.Host.Api.Data (nvimGetMode, vimCallFunction) 6 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode (..)) 7 | import Ribosome.Host.Class.Msgpack.Util (decodeString) 8 | import Ribosome.Host.Effect.Rpc (Rpc) 9 | 10 | -- |An encoding of Neovim's mode for only the most basic variants. 11 | data SimpleMode = 12 | Normal 13 | | 14 | Visual 15 | | 16 | Insert 17 | | 18 | Other Text 19 | deriving stock (Eq, Show) 20 | 21 | instance IsString SimpleMode where 22 | fromString "n" = Normal 23 | fromString "v" = Visual 24 | fromString "V" = Visual 25 | fromString "CTRL-V" = Visual 26 | fromString "i" = Insert 27 | fromString a = Other (toText a) 28 | 29 | instance MsgpackDecode SimpleMode where 30 | fromMsgpack = 31 | decodeString 32 | 33 | -- |Get the current mode as a 'SimpleMode'. 34 | simpleMode :: 35 | Member Rpc r => 36 | Sem r SimpleMode 37 | simpleMode = 38 | vimCallFunction "mode" [] 39 | 40 | -- |Indicate whether Neovim is in visual mode. 41 | visualModeActive :: 42 | Member Rpc r => 43 | Sem r Bool 44 | visualModeActive = 45 | (== Visual) <$> simpleMode 46 | 47 | -- |Get the current mode. 48 | mode :: 49 | Member Rpc r => 50 | Sem r NvimMode 51 | mode = 52 | nvimGetMode 53 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Interpreter/Reports.hs: -------------------------------------------------------------------------------- 1 | -- |Interpreters for 'Reports'. 2 | module Ribosome.Host.Interpreter.Reports where 3 | 4 | import Conc (interpretAtomic) 5 | import qualified Data.Map.Strict as Map 6 | import Polysemy.Chronos (ChronosTime) 7 | 8 | import Ribosome.Host.Data.Report (ReportContext) 9 | import qualified Ribosome.Host.Data.StoredReport as StoredReport 10 | import Ribosome.Host.Data.StoredReport (StoredReport) 11 | import qualified Ribosome.Host.Effect.Reports as Reports 12 | import Ribosome.Host.Effect.Reports (Reports) 13 | 14 | -- |Interpret 'Reports' by storing reports in 'AtomicState'. 15 | interpretReportsAtomic :: 16 | Members [AtomicState (Map ReportContext [StoredReport]), ChronosTime] r => 17 | Int -> 18 | InterpreterFor Reports r 19 | interpretReportsAtomic maxReports = 20 | interpret \case 21 | Reports.StoreReport htag msg -> do 22 | sr <- StoredReport.now msg 23 | atomicModify' (Map.alter (alter sr) htag) 24 | where 25 | alter sr = 26 | Just . take maxReports . maybe [sr] (sr :) 27 | Reports.StoredReports -> 28 | atomicGet 29 | 30 | -- |Interpret 'Reports' by storing reports in 'AtomicState' and interpret the state effect. 31 | interpretReports :: 32 | Members [ChronosTime, Embed IO] r => 33 | InterpreterFor Reports r 34 | interpretReports = 35 | interpretAtomic mempty . 36 | interpretReportsAtomic 100 . 37 | raiseUnder 38 | -------------------------------------------------------------------------------- /packages/host/test/Ribosome/Host/Test/NotifyTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Test.NotifyTest where 2 | 3 | import Conc (interpretSync) 4 | import qualified Polysemy.Conc.Sync as Sync 5 | import Polysemy.Test (UnitTest, assertJust) 6 | import Polysemy.Time (Seconds (Seconds)) 7 | 8 | import Ribosome.Host.Api.Data (nvimCallFunction) 9 | import Ribosome.Host.Class.Msgpack.Encode (toMsgpack) 10 | import Ribosome.Host.Data.Execution (Execution (Async)) 11 | import Ribosome.Host.Data.Report (Report) 12 | import Ribosome.Host.Data.RpcError (RpcError) 13 | import Ribosome.Host.Data.RpcHandler (RpcHandler) 14 | import qualified Ribosome.Host.Effect.Rpc as Rpc 15 | import Ribosome.Host.Effect.Rpc (Rpc) 16 | import Ribosome.Host.Embed (embedNvim) 17 | import Ribosome.Host.Handler (rpcFunction) 18 | import Ribosome.Host.Unit.Run (runTest) 19 | 20 | hand :: 21 | Members [Rpc !! RpcError, Sync Int, Stop Report] r => 22 | Int -> 23 | Sem r () 24 | hand = 25 | void . Sync.putWait (Seconds 5) 26 | 27 | handlers :: 28 | ∀ r . 29 | Members [Sync Int, Rpc !! RpcError] r => 30 | [RpcHandler r] 31 | handlers = 32 | [ 33 | rpcFunction "Fun" Async (hand @(Stop Report : r)) 34 | ] 35 | 36 | test_notify :: UnitTest 37 | test_notify = 38 | runTest $ interpretSync $ embedNvim handlers do 39 | Rpc.notify (nvimCallFunction @() "Fun" [toMsgpack i]) 40 | assertJust i =<< Sync.takeWait (Seconds 5) 41 | where 42 | i = 43 | 13 :: Int 44 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Effect/PersistPath.hs: -------------------------------------------------------------------------------- 1 | -- |Provide paths for 'Ribosome.Persist' 2 | module Ribosome.Effect.PersistPath where 3 | 4 | import Path (Abs, Dir, Path, Rel) 5 | 6 | import Ribosome.Data.Setting (Setting (Setting)) 7 | 8 | -- |This is a utility effect for 'Ribosome.Persist', determining the root directory for persistence files. 9 | data PersistPath :: Effect where 10 | -- |Return the root if 'Nothing' is given, or the subdir of the root if 'Just' is given. 11 | PersistPath :: Maybe (Path Rel Dir) -> PersistPath m (Path Abs Dir) 12 | 13 | makeSem_ ''PersistPath 14 | 15 | -- |Return the root if 'Nothing' is given, or the subdir of the root if 'Just' is given. 16 | persistPath :: 17 | ∀ r . 18 | Member PersistPath r => 19 | Maybe (Path Rel Dir) -> 20 | Sem r (Path Abs Dir) 21 | 22 | -- |This setting may be used to specify the root directory for all plugins. 23 | -- The default is to use the XDG cache dir. 24 | setting :: Setting (Path Abs Dir) 25 | setting = 26 | Setting "ribosome_persistence_dir" False Nothing 27 | 28 | -- |Get the root directory for persistence files. 29 | persistRoot :: 30 | Member PersistPath r => 31 | Sem r (Path Abs Dir) 32 | persistRoot = 33 | persistPath Nothing 34 | 35 | -- |Get a subdir of the root directory for persistence files. 36 | persistSubPath :: 37 | Member PersistPath r => 38 | Path Rel Dir -> 39 | Sem r (Path Abs Dir) 40 | persistSubPath p = 41 | persistPath (Just p) 42 | -------------------------------------------------------------------------------- /packages/app/lib/Ribosome/App/NewOptions.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.App.NewOptions where 2 | 3 | import Path (Dir, Path, Rel, parseRelDir) 4 | import Rainbow (chunk, fore, magenta) 5 | 6 | import qualified Ribosome.App.Data as Data 7 | import Ribosome.App.Data (FlakeUrl, NewProject (NewProject)) 8 | import Ribosome.App.Error (RainbowError, appError) 9 | import Ribosome.App.Options (NewOptions) 10 | import Ribosome.App.ProjectOptions (projectOptions) 11 | import Ribosome.App.UserInput (askRequired) 12 | 13 | defaultFlakeUrl :: FlakeUrl 14 | defaultFlakeUrl = 15 | "git+https://git.tryp.io/tek/ribosome" 16 | 17 | parseDir :: 18 | ToText a => 19 | ToString a => 20 | Member (Stop RainbowError) r => 21 | a -> 22 | Sem r (Path Rel Dir) 23 | parseDir name = 24 | stopNote invalidName (parseRelDir (toString name)) 25 | where 26 | invalidName = 27 | appError [fore magenta (chunk (toText name)), " cannot be used as a directory name."] 28 | 29 | newOptions :: 30 | Members [Stop RainbowError, Embed IO] r => 31 | NewOptions -> 32 | Sem r NewProject 33 | newOptions opts = do 34 | project <- projectOptions True (opts ^. #project) 35 | author <- maybe (askRequired "Author name for Cabal file?") pure (opts ^. #author) 36 | maintainer <- maybe (askRequired "Maintainer email for Cabal file?") pure (opts ^. #maintainer) 37 | let 38 | flakeUrl = 39 | fromMaybe defaultFlakeUrl (opts ^. #flakeUrl) 40 | printDir = 41 | opts ^. #printDir 42 | pure NewProject {..} 43 | -------------------------------------------------------------------------------- /packages/menu/test/Ribosome/Menu/Test/DeleteCursorTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Test.DeleteCursorTest where 2 | 3 | import Polysemy.Test (UnitTest, assertEq) 4 | 5 | import Ribosome.Api.Buffer (bufferContent) 6 | import Ribosome.Api.Window (windowLine) 7 | import qualified Ribosome.Data.ScratchState 8 | import Ribosome.Menu.Action (menuDelete) 9 | import Ribosome.Menu.App (defaultHandlers) 10 | import Ribosome.Menu.Data.Filter (fuzzy) 11 | import Ribosome.Menu.Data.State (modal) 12 | import qualified Ribosome.Menu.Effect.MenuTest as MenuTest 13 | import Ribosome.Menu.Effect.MenuTest (sendMappingRender) 14 | import qualified Ribosome.Menu.Effect.MenuUi as MenuUi 15 | import Ribosome.Menu.Scratch (menuScratch) 16 | import Ribosome.Menu.Test.Run (testStaticNvimMenu) 17 | import Ribosome.Menu.Test.Util (staticMenuItems) 18 | import Ribosome.Test.Embed (testEmbed_) 19 | 20 | test_deleteCursor :: UnitTest 21 | test_deleteCursor = 22 | testEmbed_ do 23 | testStaticNvimMenu its def (modal fuzzy) (menuScratch & #maxSize ?~ 4) maps do 24 | scr <- MenuUi.itemsScratch 25 | sendMappingRender "j" 26 | sendMappingRender "d" 27 | assertEq 0 =<< windowLine scr.window 28 | sendMappingRender "d" 29 | assertEq 0 =<< windowLine scr.window 30 | assertEq ["3", "4", "5", "6"] =<< bufferContent scr.buffer 31 | MenuTest.quit 32 | where 33 | maps = defaultHandlers <> [("d", menuDelete)] 34 | 35 | its = staticMenuItems (reverse (show <$> [1 :: Int .. 8])) 36 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Api.hs: -------------------------------------------------------------------------------- 1 | -- |All exports for the composite and atomic Neovim API functions. 2 | module Ribosome.Api ( 3 | module Ribosome.Api.Autocmd, 4 | module Ribosome.Api.Buffer, 5 | FileBuffer (FileBuffer), 6 | module Ribosome.Api.Echo, 7 | module Ribosome.Api.Function, 8 | module Ribosome.Api.Mode, 9 | module Ribosome.Api.Normal, 10 | module Ribosome.Api.Option, 11 | module Ribosome.Api.Path, 12 | module Ribosome.Api.Position, 13 | module Ribosome.Api.Process, 14 | module Ribosome.Api.Register, 15 | module Ribosome.Api.Sleep, 16 | module Ribosome.Api.Syntax, 17 | module Ribosome.Api.Tags, 18 | module Ribosome.Api.Tabpage, 19 | module Ribosome.Api.Undo, 20 | module Ribosome.Api.Window, 21 | module Ribosome.Api.Variable, 22 | module Ribosome.Host.Api.Data, 23 | ) where 24 | 25 | import Ribosome.Api.Autocmd 26 | import Ribosome.Api.Buffer 27 | import Ribosome.Api.Echo 28 | import Ribosome.Api.Function 29 | import Ribosome.Api.Mode 30 | import Ribosome.Api.Normal 31 | import Ribosome.Api.Option 32 | import Ribosome.Api.Path 33 | import Ribosome.Api.Position 34 | import Ribosome.Api.Process 35 | import Ribosome.Api.Register 36 | import Ribosome.Api.Sleep 37 | import Ribosome.Api.Syntax 38 | import Ribosome.Api.Tabpage 39 | import Ribosome.Api.Tags 40 | import Ribosome.Api.Undo 41 | import Ribosome.Api.Variable 42 | import Ribosome.Api.Window 43 | import Ribosome.Data.FileBuffer (FileBuffer (FileBuffer)) 44 | import Ribosome.Host.Api.Data 45 | -------------------------------------------------------------------------------- /packages/test/test/Ribosome/Test/ReportTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Test.ReportTest where 2 | 3 | import qualified Log 4 | import Log (Severity (Error, Info)) 5 | import Polysemy.Test (UnitTest) 6 | 7 | import Ribosome.Host.Api.Data (nvimCommand, nvimInput) 8 | import Ribosome.Host.Data.Execution (Execution (Sync)) 9 | import Ribosome.Host.Data.Report (Report (Report)) 10 | import Ribosome.Host.Data.RpcHandler (Handler) 11 | import Ribosome.Host.Handler (rpcFunction) 12 | import qualified Ribosome.Report as Report 13 | import Ribosome.Test.Screenshot (awaitScreenshot) 14 | import Ribosome.Test.SocketTmux (testHandlersSocketTmux) 15 | 16 | stopInfo :: Handler r () 17 | stopInfo = 18 | stop (Report "report an info Stop by echoing" [] Info) 19 | 20 | stopError :: Handler r () 21 | stopError = 22 | stop (Report "report an error Stop by responding" [] Error) 23 | 24 | test_report :: UnitTest 25 | test_report = 26 | testHandlersSocketTmux [rpcFunction "StopInfo" Sync stopInfo, rpcFunction "StopError" Sync stopError] do 27 | nvimCommand "mode" 28 | Log.debug "don't report Log.debug message" 29 | awaitScreenshot False "report-1" 0 30 | Log.info "report Log.info message" 31 | awaitScreenshot False "report-2" 0 32 | Report.warn "report Report.warn message" [] 33 | awaitScreenshot False "report-3" 0 34 | nvimInput ":call StopInfo()" 35 | awaitScreenshot False "report-4" 0 36 | nvimInput ":call StopError()" 37 | awaitScreenshot False "report-5" 0 38 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Lens.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Lens where 2 | 3 | import qualified Lens.Micro.Mtl as Lens 4 | 5 | (#.) :: Coercible c b => (b -> c) -> (a -> b) -> (a -> c) 6 | (#.) _ = 7 | coerce (\ x -> x :: b) :: forall a b. Coercible b a => a -> b 8 | 9 | view :: 10 | Member (Reader s) r => 11 | Getting a s a -> 12 | Sem r a 13 | view l = 14 | asks (getConst #. l Const) 15 | {-# inline view #-} 16 | 17 | use :: 18 | Member (State s) r => 19 | Getting a s a -> 20 | Sem r a 21 | use l = 22 | gets (Lens.view l) 23 | {-# inline use #-} 24 | 25 | (%=) :: 26 | Member (State s) r => 27 | ASetter s s a b -> 28 | (a -> b) -> 29 | Sem r () 30 | l %= f = 31 | modify' (l %~ f) 32 | {-# inline (%=) #-} 33 | 34 | infix 4 %= 35 | 36 | (.=) :: 37 | Member (State s) r => 38 | ASetter s s a b -> 39 | b -> 40 | Sem r () 41 | l .= b = 42 | modify' (l .~ b) 43 | {-# inline (.=) #-} 44 | 45 | (?=) :: 46 | Member (State s) r => 47 | ASetter s s a (Maybe b) -> 48 | b -> 49 | Sem r () 50 | l ?= b = 51 | modify' (l ?~ b) 52 | {-# inline (?=) #-} 53 | 54 | infix 4 .= 55 | 56 | (+=) :: 57 | Num a => 58 | Member (State s) r => 59 | ASetter s s a a -> 60 | a -> 61 | Sem r () 62 | l += a = 63 | l %= (+ a) 64 | {-# inline (+=) #-} 65 | 66 | infix 4 += 67 | 68 | (<.=) :: 69 | Member (State s) r => 70 | ASetter s s a b -> 71 | b -> 72 | Sem r b 73 | l <.= b = do 74 | l .= b 75 | pure b 76 | {-# inline (<.=) #-} 77 | 78 | infix 4 <.= 79 | -------------------------------------------------------------------------------- /packages/app/lib/Ribosome/App/Error.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.App.Error where 2 | 3 | import Rainbow (Chunk, chunk, faint, fore, hPutChunksLn, red) 4 | import System.IO (Handle) 5 | 6 | 7 | newtype RainbowError = 8 | RainbowError { unRainbowError :: NonEmpty (NonEmpty Chunk) } 9 | deriving newtype (Semigroup) 10 | 11 | instance IsString RainbowError where 12 | fromString = 13 | RainbowError . pure . pure . fromString 14 | 15 | appError :: [Chunk] -> RainbowError 16 | appError msg = 17 | RainbowError ["⚠️ " :| (fore red "Error ") : msg] 18 | 19 | ioError :: [Chunk] -> Text -> RainbowError 20 | ioError msg err = 21 | appError msg <> RainbowError [["🗨️ ", fore red (faint (chunk err))]] 22 | 23 | outputError :: 24 | Members [Stop RainbowError, Embed IO] r => 25 | IO a -> 26 | Sem r a 27 | outputError = 28 | stopTryIOError err 29 | where 30 | err = 31 | ioError ["Printing message failed"] 32 | 33 | runRainbowErrorAnd :: 34 | Members [Embed IO, Final IO] r => 35 | Handle -> 36 | Sem r () -> 37 | Sem (Stop RainbowError : r) () -> 38 | Sem r () 39 | runRainbowErrorAnd handle after action = do 40 | either onError pure =<< stopToIOFinal action 41 | where 42 | onError (RainbowError cs) = do 43 | tryIOError_ (traverse_ (hPutChunksLn handle . toList) cs) 44 | after 45 | 46 | runRainbowError :: 47 | Members [Embed IO, Final IO] r => 48 | Handle -> 49 | Sem (Stop RainbowError : r) () -> 50 | Sem r () 51 | runRainbowError handle = 52 | runRainbowErrorAnd handle unit 53 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Api/Syntax.hs: -------------------------------------------------------------------------------- 1 | -- |API functions for applying syntax rules to Neovim. 2 | module Ribosome.Api.Syntax where 3 | 4 | import Ribosome.Data.Syntax.Syntax (Syntax) 5 | import Ribosome.Host.Api.Data (Window, vimCallFunction) 6 | import Ribosome.Host.Class.MonadRpc (MonadRpc (atomic)) 7 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode (toMsgpack)) 8 | import Ribosome.Host.Effect.Rpc (Rpc) 9 | import Ribosome.Host.Modify (windo) 10 | import Ribosome.Internal.Syntax (catCmds, syntaxCmds) 11 | 12 | -- |Apply syntax rules to the current window. 13 | executeSyntax :: 14 | MonadRpc m => 15 | Syntax -> 16 | m () 17 | executeSyntax = 18 | catCmds . syntaxCmds 19 | 20 | -- |Apply syntax rules to a window. 21 | executeWindowSyntax :: 22 | Member Rpc r => 23 | Window -> 24 | Syntax -> 25 | Sem r () 26 | executeWindowSyntax win syntax = 27 | windo win (executeSyntax syntax) 28 | 29 | -- |Apply syntax rules to a window. 30 | executeWindowSyntaxes :: 31 | MonadRpc m => 32 | Window -> 33 | [Syntax] -> 34 | m () 35 | executeWindowSyntaxes win syntax = 36 | atomic $ windo win do 37 | traverse_ executeSyntax syntax 38 | 39 | -- |Get the name of the syntax group at a given position. 40 | syntaxName :: 41 | Member Rpc r => 42 | Int -> 43 | Int -> 44 | Sem r (Text, Text) 45 | syntaxName l c = do 46 | synId <- vimCallFunction "synID" (toMsgpack <$> [l, c, 0]) 47 | (,) <$> vimCallFunction "getline" [toMsgpack l] <*> vimCallFunction "synIDattr" [synId, toMsgpack ("name" :: Text)] 48 | -------------------------------------------------------------------------------- /packages/menu/test/Ribosome/Menu/Test/TriggerPrioTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Test.TriggerPrioTest where 2 | 3 | import Polysemy.Test (UnitTest, (===)) 4 | 5 | import Ribosome.Menu.Action (menuAttachPrompt, menuDetachPrompt, menuSuccess) 6 | import Ribosome.Menu.App (notPrompt, onlyPrompt) 7 | import Ribosome.Menu.Data.Filter (substring) 8 | import Ribosome.Menu.Data.MenuResult (MenuResult (Success)) 9 | import Ribosome.Menu.Data.State (modal) 10 | import qualified Ribosome.Menu.Effect.MenuTest as MenuTest 11 | import Ribosome.Menu.Effect.MenuTest (sendMapping, sendMappingPrompt) 12 | import Ribosome.Menu.Prompt (menuPrompt) 13 | import qualified Ribosome.Menu.Prompt.Data.Prompt 14 | import Ribosome.Menu.Scratch (menuScratch) 15 | import Ribosome.Menu.Test.Run (testStaticNvimMenu) 16 | import Ribosome.Menu.Test.Util (staticMenuItems) 17 | import Ribosome.Test.Embed (testEmbed_) 18 | 19 | test_triggerPrio :: UnitTest 20 | test_triggerPrio = do 21 | testEmbed_ do 22 | res <- testStaticNvimMenu its def (modal substring) (menuScratch & #maxSize ?~ 10) app do 23 | sendMappingPrompt "x" 24 | sendMappingPrompt "" 25 | sendMapping "q" 26 | MenuTest.result 27 | Success "detached" === (res <&> (.text)) 28 | where 29 | app = 30 | [ 31 | (notPrompt "x", menuAttachPrompt (Just "attached")), 32 | (onlyPrompt "", menuDetachPrompt (Just "detached")), 33 | (notPrompt "q", menuSuccess =<< menuPrompt) 34 | ] 35 | 36 | its = staticMenuItems (show <$> [1 :: Int .. 10]) 37 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/IOStack.hs: -------------------------------------------------------------------------------- 1 | -- |Interpreters for basic plugin effects down to 'IO' 2 | module Ribosome.IOStack where 3 | 4 | import Ribosome.Cli (withCli) 5 | import Ribosome.Data.CustomConfig (CustomConfig (CustomConfig)) 6 | import Ribosome.Data.PluginConfig (PluginConfig (PluginConfig)) 7 | import Ribosome.Data.PluginName (PluginName) 8 | import Ribosome.Host.Data.HostConfig (HostConfig) 9 | import Ribosome.Host.IOStack (BasicStack, runBasicStack) 10 | 11 | -- |The effects that are shared by all variants (like embedded, remote, socket) of main functions. 12 | -- 13 | -- Contains logging effects, IO related stuff and the plugin's name in a 'Reader'. 14 | type BasicPluginStack c = 15 | Reader PluginName : Reader (CustomConfig c) : BasicStack 16 | 17 | -- |Execute the basic plugin stack all the way to an 'IO', given the plugin name and logging settings. 18 | runBasicPluginStack :: 19 | PluginName -> 20 | HostConfig -> 21 | c -> 22 | Sem (BasicPluginStack c) () -> 23 | IO () 24 | runBasicPluginStack name conf custom = 25 | runBasicStack conf . 26 | runReader (CustomConfig custom) . 27 | runReader name 28 | 29 | -- |Execute the basic plugin stack all the way to an 'IO' like 'runBasicPluginStack', reading config overrides from 30 | -- command line options. 31 | runCli :: 32 | PluginConfig c -> 33 | Sem (BasicPluginStack c) () -> 34 | IO () 35 | runCli (PluginConfig name defaultConf customParser) prog = 36 | withCli name defaultConf customParser \ conf custom -> 37 | runBasicPluginStack name conf custom prog 38 | -------------------------------------------------------------------------------- /packages/host/test/Ribosome/Host/Test/LogTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Test.LogTest where 2 | 3 | import qualified Data.Text as Text 4 | import qualified Data.Text.IO as Text 5 | import Log (Severity (Crit)) 6 | import Path (relfile, toFilePath) 7 | import qualified Polysemy.Test as Test 8 | import Polysemy.Test (UnitTest, assertEq, assertLeft) 9 | 10 | import Ribosome.Host.Api.Data (nvimCallFunction) 11 | import Ribosome.Host.Data.Execution (Execution (Sync)) 12 | import Ribosome.Host.Data.Report (Report (Report)) 13 | import Ribosome.Host.Data.RpcError (RpcError) 14 | import Ribosome.Host.Data.RpcHandler (Handler, RpcHandler) 15 | import Ribosome.Host.Embed (embedNvim) 16 | import Ribosome.Host.Handler (rpcFunction) 17 | import Ribosome.Host.Unit.Run (runTestConf, runUnitTest) 18 | 19 | stopper :: Handler r () 20 | stopper = 21 | stop (Report "error" ["error!!", "meltdown"] Crit) 22 | 23 | handlers :: [RpcHandler r] 24 | handlers = 25 | [rpcFunction "Stopper" Sync stopper] 26 | 27 | fileTarget :: [Text] 28 | fileTarget = 29 | [ 30 | "\ESC[35m[crit] \ESC[0m [R.H.T.LogTest#21] function:Stopper:", 31 | "error!!", 32 | "meltdown" 33 | ] 34 | 35 | test_logFile :: UnitTest 36 | test_logFile = 37 | runUnitTest do 38 | file <- Test.tempFile [] [relfile|log/log|] 39 | runTestConf (def & #hostLog . #logFile ?~ file) do 40 | embedNvim handlers do 41 | assertLeft () . first unit =<< resumeEither @RpcError @_ @_ @() (nvimCallFunction "Stopper" []) 42 | assertEq fileTarget . Text.lines =<< embed (Text.readFile (toFilePath file)) 43 | -------------------------------------------------------------------------------- /packages/app/lib/Ribosome/App/NewProject.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.App.NewProject where 2 | 3 | import qualified Data.Text.IO as Text 4 | import Polysemy.Chronos (ChronosTime) 5 | 6 | import Ribosome.App.Boot (generateBoot) 7 | import Ribosome.App.Data (Global (..), NewProject (..), Project (..), unPrintDir) 8 | import qualified Ribosome.App.Data 9 | import Ribosome.App.Error (RainbowError) 10 | import Ribosome.App.TemplateTree (writeTemplateTree) 11 | import Ribosome.App.Templates (newProjectTemplates) 12 | import Ribosome.App.UserInput (cmdColor, infoMessage, neovimChunk, pathChunk, pathColor, putStderr) 13 | import Ribosome.Host.Path (pathText) 14 | import Exon (exon) 15 | 16 | newProject :: 17 | Members [ChronosTime, Stop RainbowError, Embed IO] r => 18 | Global -> 19 | NewProject -> 20 | Sem r () 21 | newProject global NewProject {project = pro@Project {..}, ..} = do 22 | writeTemplateTree global directory (newProjectTemplates names flakeUrl author maintainer branch github cachix) 23 | unless (global ^. #quiet) do 24 | infoMessage [ 25 | "🌝 Initialized a ", 26 | neovimChunk, 27 | " plugin project in ", 28 | pathChunk directory, 29 | "." 30 | ] 31 | infoMessage [ 32 | "Run ", 33 | cmdColor [exon|nix build .#{"#"}#{fromText names.name.unProjectName}.static|], 34 | " in that directory to create a statically linked executable in ", 35 | pathColor "result/bin", 36 | "." 37 | ] 38 | putStderr "" 39 | generateBoot global pro 40 | when printDir.unPrintDir (embed (Text.putStrLn (pathText directory))) 41 | -------------------------------------------------------------------------------- /packages/ribosome/test/Ribosome/Test/WindowTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Test.WindowTest where 2 | 3 | import Polysemy.Test (UnitTest, assertEq, unitTest, (===)) 4 | import Test.Tasty (TestTree, testGroup) 5 | 6 | import Ribosome.Api.Window (ensureMainWindow) 7 | import Ribosome.Host.Api.Data (Window) 8 | import Ribosome.Host.Api.Data (bufferSetOption, nvimCommand, vimGetCurrentBuffer, vimGetCurrentWindow, vimGetWindows) 9 | import Ribosome.Host.Class.Msgpack.Encode (toMsgpack) 10 | import Ribosome.Host.Effect.Rpc (Rpc) 11 | import Ribosome.Host.Test.Run (embedTest_) 12 | 13 | setCurrentNofile :: 14 | Member Rpc r => 15 | Sem r () 16 | setCurrentNofile = do 17 | buf <- vimGetCurrentBuffer 18 | bufferSetOption buf "buftype" (toMsgpack ("nofile" :: Text)) 19 | 20 | createNofile :: 21 | Member Rpc r => 22 | Sem r Window 23 | createNofile = do 24 | initialWindow <- vimGetCurrentWindow 25 | nvimCommand "new" 26 | setCurrentNofile 27 | pure initialWindow 28 | 29 | test_findMainWindowExisting :: UnitTest 30 | test_findMainWindowExisting = 31 | embedTest_ do 32 | initialWindow <- createNofile 33 | (initialWindow ===) =<< ensureMainWindow 34 | 35 | test_findMainWindowCreate :: UnitTest 36 | test_findMainWindowCreate = 37 | embedTest_ do 38 | setCurrentNofile 39 | void createNofile 40 | void ensureMainWindow 41 | assertEq 3 . length =<< vimGetWindows 42 | 43 | test_window :: TestTree 44 | test_window = 45 | testGroup "window" [ 46 | unitTest "find existing" test_findMainWindowExisting, 47 | unitTest "find create" test_findMainWindowCreate 48 | ] 49 | -------------------------------------------------------------------------------- /packages/host/test/Ribosome/Host/Test/ApiInfoTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Test.ApiInfoTest where 2 | 3 | import Polysemy.Test (Hedgehog, UnitTest, assertLeft, assertRight, evalEither, runTestAuto, (===)) 4 | 5 | import Ribosome.Host.Class.Msgpack.Error (renderError) 6 | import Ribosome.Host.Data.ApiInfo (apiInfo, functions, types) 7 | import Ribosome.Host.Data.ApiType ( 8 | ApiPrim (Boolean, Dictionary, Float, Integer, LuaRef, Object, String, Void), 9 | ApiType (Array, Ext, Prim), 10 | parseApiType, 11 | ) 12 | 13 | checkType :: 14 | Member (Hedgehog IO) r => 15 | ApiType -> 16 | ByteString -> 17 | Sem r () 18 | checkType target spec = 19 | assertRight target (parseApiType spec) 20 | 21 | test_parseType :: UnitTest 22 | test_parseType = 23 | runTestAuto do 24 | checkType (Array (Prim Integer) (Just 5)) "ArrayOf(Integer, 5)" 25 | checkType (Array (Array (Ext "Buffer") (Just 2)) (Just 3)) "ArrayOf(ArrayOf(Buffer, 2), 3)" 26 | checkType (Array (Prim Boolean) Nothing) "ArrayOf(Boolean)" 27 | checkType (Array (Prim Object) Nothing) "Array" 28 | checkType (Prim Float) "Float" 29 | checkType (Prim String) "String" 30 | checkType (Prim Dictionary) "Dictionary" 31 | checkType (Prim Object) "Object" 32 | checkType (Prim Void) "void" 33 | checkType (Prim LuaRef) "LuaRef" 34 | assertLeft err (first renderError (parseApiType "Booleans")) 35 | info <- evalEither =<< embed apiInfo 36 | 255 === length info.functions 37 | 3 === length info.types 38 | where 39 | err = 40 | "Decoding ApiType: Parsed (Prim Boolean) but got leftovers: s" 41 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/RenderMenu.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.RenderMenu where 2 | 3 | import Ribosome.Menu.Class.MenuMode (MenuMode (renderExtra, renderFilter)) 4 | import Ribosome.Menu.Class.MenuState (MenuState (Item, core, mode, renderStatus)) 5 | import Ribosome.Menu.Data.CursorIndex (CursorIndex (CursorIndex)) 6 | import Ribosome.Menu.Data.Entry (Entries) 7 | import Ribosome.Menu.Data.MenuAction (RenderAnchor) 8 | import Ribosome.Menu.Data.MenuStatus (MenuStatus (MenuStatus)) 9 | import qualified Ribosome.Menu.Data.State as Modal 10 | import Ribosome.Menu.Data.State (Core (Core), Primary (Primary)) 11 | import Ribosome.Menu.Data.WithCursor (WithCursor (WithCursor)) 12 | 13 | data RenderMenu i = 14 | RenderMenu { 15 | entries :: Entries i, 16 | cursor :: CursorIndex, 17 | status :: MenuStatus, 18 | anchor :: RenderAnchor 19 | } 20 | deriving stock (Generic) 21 | 22 | menuStatus :: 23 | ∀ s . 24 | MenuState s => 25 | Word -> 26 | Word -> 27 | CursorIndex -> 28 | s -> 29 | MenuStatus 30 | menuStatus itemCount entryCount (CursorIndex cursor) s = 31 | MenuStatus (renderFilter @(Item s) md) (renderExtra @(Item s) md) (renderStatus s) itemCount entryCount cursor 32 | where 33 | md = s ^. mode 34 | 35 | fromState :: 36 | ∀ s . 37 | MenuState s => 38 | WithCursor s -> 39 | RenderAnchor -> 40 | RenderMenu (Item s) 41 | fromState (WithCursor s cursor) anchor = 42 | RenderMenu {status = menuStatus @s itemCount entryCount cursor s, ..} 43 | where 44 | Core {primary = Primary {entries}, itemCount, entryCount} = 45 | s ^. core 46 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Remote.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Remote where 2 | 3 | import Ribosome.Host.Data.HostConfig (HostConfig) 4 | import Ribosome.Host.Data.Report (Report) 5 | import Ribosome.Host.Data.RpcHandler (RpcHandler) 6 | import Ribosome.Host.Effect.Handlers (Handlers) 7 | import Ribosome.Host.IOStack (BasicStack, IOStack, runBasicStack) 8 | import Ribosome.Host.Interpreter.Handlers (interpretHandlers) 9 | import Ribosome.Host.Interpreter.Host (runHost) 10 | import Ribosome.Host.Interpreter.Process.Stdio (interpretProcessCerealStdio) 11 | import Ribosome.Host.Interpreter.UserError (interpretUserErrorInfo) 12 | import Ribosome.Host.Run (RpcDeps, RpcStack, interpretRpcStack) 13 | 14 | type HostRemoteStack = 15 | RpcStack ++ RpcDeps 16 | 17 | type HostRemoteIOStack = 18 | HostRemoteStack ++ BasicStack 19 | 20 | interpretRpcDeps :: 21 | Members IOStack r => 22 | InterpretersFor RpcDeps r 23 | interpretRpcDeps = 24 | interpretUserErrorInfo . 25 | interpretProcessCerealStdio 26 | 27 | interpretHostRemote :: 28 | Members BasicStack r => 29 | InterpretersFor HostRemoteStack r 30 | interpretHostRemote = 31 | interpretRpcDeps . 32 | interpretRpcStack 33 | 34 | runHostRemote :: 35 | Members BasicStack r => 36 | InterpreterFor (Handlers !! Report) (HostRemoteStack ++ r) -> 37 | Sem r () 38 | runHostRemote handlers = 39 | interpretHostRemote (handlers runHost) 40 | 41 | runHostRemoteIO :: 42 | HostConfig -> 43 | [RpcHandler HostRemoteIOStack] -> 44 | IO () 45 | runHostRemoteIO conf handlers = 46 | runBasicStack conf $ 47 | runHostRemote (interpretHandlers handlers) 48 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/ApiInfo.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Data.ApiInfo where 2 | 3 | import Data.MessagePack (Object) 4 | import qualified Data.Serialize as Serialize 5 | import System.Process.Typed (proc, readProcessStdout_) 6 | 7 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode, nestedDecode) 8 | import Ribosome.Host.Class.Msgpack.Error (DecodeError, FieldError, toDecodeError) 9 | import Ribosome.Host.Data.ApiType (ApiType) 10 | 11 | data RpcDecl = 12 | RpcDecl { 13 | name :: String, 14 | parameters :: [(ApiType, String)], 15 | since :: Maybe Int64, 16 | deprecated_since :: Maybe Int64, 17 | method :: Bool, 18 | return_type :: ApiType 19 | } 20 | deriving stock (Eq, Show, Generic) 21 | deriving anyclass (MsgpackDecode) 22 | 23 | newtype ExtType = 24 | ExtType { unExtType :: String } 25 | deriving stock (Eq, Show) 26 | deriving newtype (IsString, Ord, MsgpackDecode) 27 | 28 | data ExtTypeMeta = 29 | ExtTypeMeta { 30 | id :: Int64, 31 | prefix :: String 32 | } 33 | deriving stock (Eq, Show, Generic) 34 | deriving anyclass (MsgpackDecode) 35 | 36 | data ApiInfo = 37 | ApiInfo { 38 | types :: Map ExtType ExtTypeMeta, 39 | functions :: [RpcDecl] 40 | } 41 | deriving stock (Eq, Show, Generic) 42 | deriving anyclass (MsgpackDecode) 43 | 44 | msgpack :: IO (Either FieldError Object) 45 | msgpack = 46 | first fromString . Serialize.decode . toStrict <$> readProcessStdout_ (proc "nvim" ["--api-info"]) 47 | 48 | apiInfo :: IO (Either DecodeError ApiInfo) 49 | apiInfo = 50 | toDecodeError . (nestedDecode =<<) <$> msgpack 51 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/InputParams.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.InputParams where 2 | 3 | import Exon (exon) 4 | 5 | import Ribosome.Data.Mapping (MapMode, MappingLhs) 6 | import qualified Ribosome.Menu.Prompt.Data.Prompt 7 | import Ribosome.Menu.Prompt.Data.Prompt ( 8 | Prompt (Prompt), 9 | PromptControl (PromptControlApp, PromptControlItems), 10 | PromptState (PromptState), 11 | ) 12 | import Ribosome.Menu.Prompt.Data.PromptMode (toMapMode) 13 | 14 | data InputTrigger = 15 | InputMapping MappingLhs 16 | | 17 | InputPrompt 18 | deriving stock (Eq, Show, Ord) 19 | 20 | data InputMode = 21 | InputMode { 22 | mode :: MapMode, 23 | prompt :: PromptControl 24 | } 25 | deriving stock (Eq, Show, Ord) 26 | 27 | describeInputMode :: InputMode -> Text 28 | describeInputMode InputMode {..} = 29 | [exon|#{show mode} (#{pc})|] 30 | where 31 | pc = case prompt of 32 | PromptControlApp -> "app" 33 | PromptControlItems -> "items" 34 | 35 | promptMode :: PromptState -> InputMode 36 | promptMode PromptState {prompt = Prompt {mode}, control} = 37 | InputMode {mode = toMapMode mode, prompt = control} 38 | 39 | data InputParams = 40 | InputParams { 41 | trigger :: InputTrigger, 42 | mode :: InputMode 43 | } 44 | deriving stock (Eq, Show, Ord) 45 | 46 | inputPrompt :: PromptState -> InputParams 47 | inputPrompt s = 48 | InputParams {trigger = InputPrompt, mode = promptMode s} 49 | 50 | data InputDomain = 51 | InputDomain { 52 | modes :: NonEmpty MapMode, 53 | prompt :: Maybe PromptControl 54 | } 55 | deriving stock (Eq, Show, Ord, Generic) 56 | -------------------------------------------------------------------------------- /packages/menu/test/Ribosome/Menu/Test/CursorClampTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Test.CursorClampTest where 2 | 3 | import Polysemy.Test (UnitTest, assertEq) 4 | 5 | import Ribosome.Api.Buffer (bufferContent) 6 | import qualified Ribosome.Data.ScratchState 7 | import Ribosome.Menu.App (defaultHandlers) 8 | import Ribosome.Menu.Data.Filter (substring) 9 | import Ribosome.Menu.Data.MenuEvent (MenuEvent (Query), QueryEvent (Refined)) 10 | import Ribosome.Menu.Data.State (modal) 11 | import qualified Ribosome.Menu.Effect.MenuTest as MenuTest 12 | import Ribosome.Menu.Effect.MenuTest (sendMappingRender, sendPrompt, waitEvent) 13 | import qualified Ribosome.Menu.Effect.MenuUi as MenuUi 14 | import Ribosome.Menu.Prompt.Data.Prompt (Prompt (Prompt)) 15 | import Ribosome.Menu.Prompt.Data.PromptMode (PromptMode (Insert)) 16 | import Ribosome.Menu.Scratch (menuScratch) 17 | import Ribosome.Menu.Test.Run (testStaticNvimMenu) 18 | import Ribosome.Menu.Test.Util (staticMenuItems) 19 | import Ribosome.Test.Embed (testEmbed_) 20 | import Ribosome.Test.Wait (assertWait) 21 | 22 | test_clampCursor :: UnitTest 23 | test_clampCursor = do 24 | testEmbed_ do 25 | testStaticNvimMenu its def (modal substring) (menuScratch & #maxSize ?~ 10) maps do 26 | scr <- MenuUi.itemsScratch 27 | sendMappingRender "j" 28 | sendPrompt (Prompt 1 Insert "1") 29 | waitEvent "1" (Query Refined) 30 | assertWait (bufferContent scr.buffer) (assertEq ["11", "12", "13"]) 31 | MenuTest.quit 32 | where 33 | maps = defaultHandlers 34 | 35 | its = staticMenuItems (reverse ["11", "12", "13", "44", "55", "66", "77", "88", "99"]) 36 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | _Ribosome_ is a suite of libraries for building [Neovim](https://neovim.io) remote plugins in Haskell, using the 4 | algebraic effect system [Polysemy](https://hackage.haskell.org/package/polysemy) as its foundation. 5 | 6 | Its components are: 7 | 8 | * [ribosome](https://hackage.haskell.org/package/ribosome/docs/Ribosome.html) High-level functionality for writing 9 | plugins 10 | * [ribosome-host](https://hackage.haskell.org/package/ribosome-host/docs/Ribosome-Host.html) MessagePack RPC server and 11 | TH-generated API functions 12 | * [ribosome-host-test](https://hackage.haskell.org/package/ribosome-host-test/docs/Ribosome-Host-Test.html) Test 13 | utilities for `ribosome-host` 14 | * [ribosome-test](https://hackage.haskell.org/package/ribosome-test/docs/Ribosome-Test.html) Test utilities for 15 | `ribosome` 16 | * [ribosome-menu](https://hackage.haskell.org/package/ribosome-menu/docs/Ribosome-Menu.html) A fuzzy-finder menu tool 17 | 18 | # Quickstart 19 | 20 | Install the [Nix package manager](https://nixos.org/learn.html) and generate a skeleton project by running: 21 | 22 | ```bash 23 | $ nix run 'github:tek/ribosome#new' 24 | ``` 25 | 26 | The new project will contain configuration for Github Actions that release binary executables on each push that will be 27 | downloaded automatically when a user starts the plugin for the first time, with support for pushing to and downloading 28 | from [Cachix](https://app.cachix.org/). 29 | 30 | After initial generation, the Github Actions and Neovim boot files can be regenerated by running: 31 | 32 | ```bash 33 | $ nix run '.#boot' 34 | ``` 35 | -------------------------------------------------------------------------------- /packages/host/test/Ribosome/Host/Test/CommandBangTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Test.CommandBangTest where 2 | 3 | import Conc (interpretAtomic) 4 | import Polysemy.Test (UnitTest, assertJust) 5 | 6 | import Ribosome.Host.Api.Data (nvimCommand, nvimGetVar, nvimSetVar) 7 | import Ribosome.Host.Class.Msgpack.Encode (toMsgpack) 8 | import Ribosome.Host.Data.Bang (Bang (Bang, NoBang)) 9 | import Ribosome.Host.Data.Execution (Execution (Sync)) 10 | import Ribosome.Host.Data.Report (Report, resumeReport) 11 | import Ribosome.Host.Data.RpcError (RpcError) 12 | import Ribosome.Host.Data.RpcHandler (RpcHandler) 13 | import Ribosome.Host.Effect.Rpc (Rpc) 14 | import Ribosome.Host.Embed (embedNvim) 15 | import Ribosome.Host.Handler (rpcCommand) 16 | import Ribosome.Host.Unit.Run (runTest) 17 | 18 | var :: Text 19 | var = 20 | "test_var" 21 | 22 | bang :: 23 | Members [Rpc !! RpcError, Stop Report] r => 24 | Bang -> 25 | Int64 -> 26 | Sem r () 27 | bang = \case 28 | Bang -> 29 | \ i -> resumeReport (nvimSetVar @[_] var [toMsgpack True, toMsgpack i]) 30 | NoBang -> 31 | \ i -> resumeReport (nvimSetVar @[_] var [toMsgpack False, toMsgpack i]) 32 | 33 | handlers :: 34 | ∀ r . 35 | Members [AtomicState Int, Rpc !! RpcError] r => 36 | [RpcHandler r] 37 | handlers = 38 | [ 39 | rpcCommand "Bang" Sync (bang @(Stop Report : r)) 40 | ] 41 | 42 | test_bang :: UnitTest 43 | test_bang = 44 | runTest $ interpretAtomic 0 $ embedNvim handlers do 45 | nvimCommand "Bang! 9" 46 | assertJust @(_, Int) (True, 9) =<< nvimGetVar var 47 | nvimCommand "Bang 10" 48 | assertJust @(_, Int) (False, 10) =<< nvimGetVar var 49 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Api/Echo.hs: -------------------------------------------------------------------------------- 1 | -- |API functions for echoing messages in Neovim. 2 | module Ribosome.Api.Echo where 3 | 4 | import Ribosome.Data.PluginName (PluginName) 5 | import Ribosome.Host.Api.Data (nvimEcho) 6 | import Ribosome.Host.Class.Msgpack.Array (msgpackArray) 7 | import Ribosome.Host.Effect.Rpc (Rpc) 8 | import Ribosome.PluginName (pluginNamePrefixed) 9 | 10 | -- |Echo a string, adding it to the message history if the first argument is 'True'. 11 | simpleEcho :: 12 | Member Rpc r => 13 | Bool -> 14 | Text -> 15 | Sem r () 16 | simpleEcho history msg = 17 | nvimEcho [msgpackArray msg] history mempty 18 | 19 | -- |Echo a string prefixed with the plugin name, adding it to the message history if the first argument is 'True'. 20 | prefixedEcho :: 21 | Members [Rpc, Reader PluginName] r => 22 | Bool -> 23 | Text -> 24 | Sem r () 25 | prefixedEcho history msg = do 26 | pref <- pluginNamePrefixed msg 27 | simpleEcho history pref 28 | 29 | -- |Echo a string with a highlight group. 30 | echohl :: 31 | Member Rpc r => 32 | Bool -> 33 | Text -> 34 | Text -> 35 | Sem r () 36 | echohl history hl msg = 37 | nvimEcho [msgpackArray msg hl] history mempty 38 | 39 | -- |Echo a string prefixed with the plugin name, without adding it to the message history. 40 | echo :: 41 | Members [Rpc, Reader PluginName] r => 42 | Text -> 43 | Sem r () 44 | echo = 45 | prefixedEcho False 46 | 47 | -- |Echo a string prefixed with the plugin name and add it to the message history. 48 | echom :: 49 | Members [Rpc, Reader PluginName] r => 50 | Text -> 51 | Sem r () 52 | echom = 53 | prefixedEcho True 54 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Socket.hs: -------------------------------------------------------------------------------- 1 | -- |Main function combinators for connecting to Neovim over a socket. 2 | module Ribosome.Socket where 3 | 4 | import Ribosome.Host.Data.BootError (BootError (BootError)) 5 | import Ribosome.Host.Data.NvimSocket (NvimSocket) 6 | import Ribosome.Host.Interpreter.Handlers (interpretHandlersNull) 7 | import Ribosome.Host.Interpreter.Process.Socket (interpretProcessCerealSocket) 8 | import Ribosome.Host.Run (RpcDeps, RpcStack, interpretRpcStack) 9 | import Ribosome.IOStack (BasicPluginStack) 10 | import Ribosome.Interpreter.Scratch (interpretScratch) 11 | import Ribosome.Interpreter.Settings (interpretSettingsRpc) 12 | import Ribosome.Interpreter.UserError (interpretUserErrorPrefixed) 13 | import Ribosome.Interpreter.VariableWatcher (interpretVariableWatcherNull) 14 | import Ribosome.Run (PluginEffects) 15 | 16 | -- |The stack of plugin internals. 17 | type SocketHandlerEffects = 18 | PluginEffects ++ RpcStack ++ RpcDeps 19 | 20 | -- |The complete stack of a Neovim plugin. 21 | type PluginSocketStack c = 22 | SocketHandlerEffects ++ Reader NvimSocket : BasicPluginStack c 23 | 24 | -- |Run plugin internals without IO effects. 25 | interpretPluginSocket :: 26 | Members (BasicPluginStack c) r => 27 | Member (Reader NvimSocket) r => 28 | InterpretersFor SocketHandlerEffects r 29 | interpretPluginSocket = 30 | interpretUserErrorPrefixed . 31 | interpretProcessCerealSocket def . 32 | resumeHoistError (BootError . show @Text) . 33 | raiseUnder . 34 | interpretRpcStack . 35 | interpretHandlersNull . 36 | interpretVariableWatcherNull . 37 | interpretSettingsRpc . 38 | interpretScratch 39 | -------------------------------------------------------------------------------- /packages/ribosome/readme.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | _Ribosome_ is a suite of libraries for building [Neovim](https://neovim.io) remote plugins in Haskell, using the 4 | algebraic effect system [Polysemy](https://hackage.haskell.org/package/polysemy) as its foundation. 5 | 6 | Its components are: 7 | 8 | * [ribosome](https://hackage.haskell.org/package/ribosome/docs/Ribosome.html) High-level functionality for writing 9 | plugins 10 | * [ribosome-host](https://hackage.haskell.org/package/ribosome-host/docs/Ribosome-Host.html) MessagePack RPC server and 11 | TH-generated API functions 12 | * [ribosome-host-test](https://hackage.haskell.org/package/ribosome-host-test/docs/Ribosome-Host-Test.html) Test 13 | utilities for `ribosome-host` 14 | * [ribosome-test](https://hackage.haskell.org/package/ribosome-test/docs/Ribosome-Test.html) Test utilities for 15 | `ribosome` 16 | * [ribosome-menu](https://hackage.haskell.org/package/ribosome-menu/docs/Ribosome-Menu.html) A fuzzy-finder menu tool 17 | 18 | # Quickstart 19 | 20 | Install the [Nix package manager](https://nixos.org/learn.html) and generate a skeleton project by running: 21 | 22 | ```bash 23 | $ nix run 'github:tek/ribosome#new' 24 | ``` 25 | 26 | The new project will contain configuration for Github Actions that release binary executables on each push that will be 27 | downloaded automatically when a user starts the plugin for the first time, with support for pushing to and downloading 28 | from [Cachix](https://app.cachix.org/). 29 | 30 | After initial generation, the Github Actions and Neovim boot files can be regenerated by running: 31 | 32 | ```bash 33 | $ nix run '.#boot' 34 | ``` 35 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/RegisterType.hs: -------------------------------------------------------------------------------- 1 | -- |Codec data type for Neovim register types. 2 | module Ribosome.Data.RegisterType where 3 | 4 | import Prettyprinter (Pretty (pretty)) 5 | 6 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode (..)) 7 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode (..)) 8 | import Ribosome.Host.Class.Msgpack.Util (decodeString) 9 | 10 | -- |The type of a Neovim register, corresponding to concepts like line- or character-wise visual mode. 11 | data RegisterType = 12 | Character 13 | | 14 | Line 15 | | 16 | Block 17 | | 18 | BlockWidth Int 19 | | 20 | Unknown Text 21 | deriving stock (Eq, Show, Ord) 22 | 23 | instance IsString RegisterType where 24 | fromString "v" = 25 | Character 26 | fromString "V" = 27 | Line 28 | fromString a@('c' : 'v' : _) = 29 | Unknown (toText a) 30 | fromString a = 31 | Unknown (toText a) 32 | 33 | instance MsgpackDecode RegisterType where 34 | fromMsgpack = 35 | decodeString 36 | 37 | instance MsgpackEncode RegisterType where 38 | toMsgpack Character = 39 | toMsgpack ("v" :: Text) 40 | toMsgpack Line = 41 | toMsgpack ("V" :: Text) 42 | toMsgpack Block = 43 | toMsgpack ("b" :: Text) 44 | toMsgpack (BlockWidth width) = 45 | toMsgpack ("b" <> show width :: Text) 46 | toMsgpack (Unknown _) = 47 | toMsgpack ("" :: Text) 48 | 49 | instance Pretty RegisterType where 50 | pretty = \case 51 | Character -> 52 | "c" 53 | Line -> 54 | "v" 55 | Block -> 56 | "" 57 | BlockWidth width -> 58 | "" <> pretty width 59 | Unknown a -> 60 | pretty a 61 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/TestMenuConfig.hs: -------------------------------------------------------------------------------- 1 | {-# options_ghc -Wno-orphans #-} 2 | 3 | module Ribosome.Menu.Data.TestMenuConfig where 4 | 5 | import Streamly.Prelude (SerialT) 6 | import Time (NanoSeconds) 7 | 8 | import Ribosome.Menu.Data.MenuItem (MenuItem) 9 | import Ribosome.Menu.Prompt.Data.Prompt (PromptModes (StartInsert), PromptState) 10 | 11 | newtype TestTimeout = 12 | TestTimeout { unTestTimeout :: NanoSeconds } 13 | deriving stock (Eq, Show) 14 | 15 | instance Show (SerialT IO (MenuItem i)) where 16 | showsPrec _ _ = showString "" 17 | 18 | data TestMenuConfig i = 19 | TestMenuConfig { 20 | items :: Maybe (SerialT IO (MenuItem i)), 21 | nativePrompt :: Maybe Bool, 22 | initialItems :: Maybe Bool, 23 | prompt :: Maybe PromptState, 24 | addBuiltin :: Maybe Bool, 25 | addDefault :: Maybe Bool, 26 | timeout :: Maybe TestTimeout 27 | } 28 | deriving stock (Show, Generic) 29 | deriving anyclass (Default) 30 | 31 | confDefault :: Lens' (TestMenuConfig i) (Maybe a) -> a -> TestMenuConfig i -> TestMenuConfig i 32 | confDefault attr a = 33 | attr %~ (<|> Just a) 34 | 35 | confSet :: Lens' (TestMenuConfig i) (Maybe a) -> a -> TestMenuConfig i -> TestMenuConfig i 36 | confSet attr a = 37 | attr .~ Just a 38 | 39 | noItems :: TestMenuConfig i -> TestMenuConfig i 40 | noItems = confSet #initialItems False 41 | 42 | noItemsConf :: TestMenuConfig i 43 | noItemsConf = noItems def 44 | 45 | startInsert :: TestMenuConfig i -> TestMenuConfig i 46 | startInsert = 47 | confSet #prompt (def & #modes .~ StartInsert) 48 | 49 | startInsertConf :: TestMenuConfig i 50 | startInsertConf = startInsert def 51 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/IOStack.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.IOStack where 2 | 3 | import Conc (ConcStack) 4 | import qualified Data.Text.IO as Text 5 | import Polysemy.Chronos (ChronosTime, interpretTimeChronos) 6 | import System.IO (stderr) 7 | 8 | import Ribosome.Host.Config (interpretLogConfig) 9 | import Ribosome.Host.Data.BootError (BootError (BootError)) 10 | import Ribosome.Host.Data.HostConfig (HostConfig, LogConfig) 11 | import Ribosome.Host.Effect.Log (FileLog, StderrLog) 12 | import Ribosome.Host.Interpreter.Log (interpretLogStderrFile, interpretLogs) 13 | 14 | type LogConfStack = 15 | [ 16 | Log, 17 | StderrLog, 18 | FileLog, 19 | Reader LogConfig, 20 | Reader HostConfig 21 | ] 22 | 23 | interpretLogConfStack :: 24 | Members [ChronosTime, Error BootError, Resource, Race, Async, Embed IO] r => 25 | HostConfig -> 26 | InterpretersFor LogConfStack r 27 | interpretLogConfStack conf = 28 | runReader conf . 29 | interpretLogConfig . 30 | interpretLogs . 31 | interpretLogStderrFile 32 | 33 | type IOStack = 34 | [ 35 | ChronosTime, 36 | Error BootError 37 | ] ++ ConcStack 38 | 39 | errorStderr :: IO (Either BootError ()) -> IO () 40 | errorStderr ma = 41 | ma >>= \case 42 | Left (BootError err) -> Text.hPutStrLn stderr err 43 | Right () -> unit 44 | 45 | runIOStack :: 46 | Sem IOStack () -> 47 | IO () 48 | runIOStack = 49 | errorStderr . 50 | runConc . 51 | errorToIOFinal . 52 | interpretTimeChronos 53 | 54 | type BasicStack = 55 | LogConfStack ++ IOStack 56 | 57 | runBasicStack :: 58 | HostConfig -> 59 | Sem BasicStack () -> 60 | IO () 61 | runBasicStack conf = 62 | runIOStack . 63 | interpretLogConfStack conf 64 | -------------------------------------------------------------------------------- /packages/menu/test/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Polysemy.Test (unitTest, unitTestTimes) 4 | import Ribosome.Menu.Test.BasicTest (test_basic) 5 | import Ribosome.Menu.Test.BottomStatusTest (test_bottomStatus) 6 | import Ribosome.Menu.Test.CursorClampTest (test_clampCursor) 7 | import Ribosome.Menu.Test.EditTest (test_editMode) 8 | import Ribosome.Menu.Test.FilterTest (test_filterFuzzy, test_filterNoSort) 9 | import Ribosome.Menu.Test.MultilineTest (test_multiline, test_multilineCramped) 10 | import Ribosome.Menu.Test.NativeInputTest (test_nativeInput) 11 | import Ribosome.Menu.Test.NoMatchTest (test_filterNoMatch) 12 | import Ribosome.Menu.Test.NvimMenuTest (test_nvimMenu) 13 | import Ribosome.Menu.Test.SliceTest (test_slice) 14 | import Ribosome.Menu.Test.TriggerPrioTest (test_triggerPrio) 15 | import Ribosome.Test.Skip (requireX) 16 | import Test.Tasty (TestTree, defaultMain, testGroup) 17 | 18 | tests :: TestTree 19 | tests = 20 | testGroup "menu" [ 21 | test_basic, 22 | test_nvimMenu, 23 | test_nativeInput, 24 | requireX (unitTestTimes 3 "extra bottom status message") test_bottomStatus, 25 | unitTest "fuzzy filter" test_filterFuzzy, 26 | unitTest "fuzzy filter without sorting on empty query" test_filterNoSort, 27 | test_slice, 28 | unitTest "multiline menu entries" test_multiline, 29 | unitTest "multiline with little space" test_multilineCramped, 30 | unitTest "query with no match" test_filterNoMatch, 31 | unitTest "clamp cursor after refining" test_clampCursor, 32 | unitTest "use the menu as a kv editor" test_editMode, 33 | unitTest "trigger priority" test_triggerPrio 34 | ] 35 | 36 | main :: IO () 37 | main = 38 | defaultMain tests 39 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/NvimMenuState.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.NvimMenuState where 2 | 3 | import qualified Ribosome.Menu.Data.Entry 4 | import Ribosome.Menu.Data.Entry (Entry, ItemIndex) 5 | import qualified Ribosome.Menu.Data.MenuItem 6 | import Ribosome.Menu.Data.MenuView (EntryIndex) 7 | 8 | data PartialEntry a = 9 | PartialEntry { 10 | entry :: Entry a, 11 | visibleLines :: Word 12 | } 13 | deriving stock (Eq, Show, Generic, Functor) 14 | 15 | partialLength :: Maybe (PartialEntry a) -> Word 16 | partialLength = \case 17 | Just e -> e.visibleLines 18 | Nothing -> 0 19 | 20 | data EntrySlice i = 21 | EntrySlice { 22 | full :: [Entry i], 23 | indexBot :: EntryIndex, 24 | indexTop :: EntryIndex, 25 | partialBot :: Maybe (PartialEntry i), 26 | partialTop :: Maybe (PartialEntry i) 27 | } 28 | | 29 | OnlyPartialEntry { 30 | entry :: PartialEntry i, 31 | index :: EntryIndex 32 | } 33 | deriving stock (Eq, Show, Generic, Functor) 34 | 35 | sliceRange :: EntrySlice i -> (EntryIndex, EntryIndex) 36 | sliceRange = \case 37 | EntrySlice {..} -> (indexBot, indexTop) 38 | OnlyPartialEntry {index} -> (index, index) 39 | 40 | sliceLength :: EntrySlice i -> Word 41 | sliceLength = \case 42 | EntrySlice {full, partialBot, partialTop} -> 43 | sum (full <&> \ e -> e.item.lines) + partialLength partialBot + partialLength partialTop 44 | OnlyPartialEntry {entry} -> entry.visibleLines 45 | 46 | data SliceIndexes = 47 | SliceIndexes { 48 | full :: [(ItemIndex, Bool)], 49 | partialBot :: Maybe (ItemIndex, Word, Bool), 50 | partialTop :: Maybe (ItemIndex, Word, Bool) 51 | } 52 | deriving stock (Eq, Show, Generic) 53 | deriving anyclass (Default) 54 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Data/MenuAction.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Data.MenuAction where 2 | 3 | import Exon (exon) 4 | 5 | import qualified Ribosome.Menu.Data.MenuResult as MenuResult 6 | import Ribosome.Menu.Data.MenuResult (MenuResult) 7 | import Ribosome.Menu.Prompt.Data.Prompt (Prompt, PromptState) 8 | 9 | -- | What to attempt to keep fixed when rendering updated state. 10 | data RenderAnchor = 11 | -- | Attempt to keep the displayed cursor line the same, shift the displayed range of items so that the cursor index 12 | -- moves to the cursor line. 13 | -- This should be used for scrolling pagewise and entry changes. 14 | AnchorLine 15 | | 16 | -- | Attempt to keep the cursor index in the same displayed line, moving the cursor line to the index line. 17 | -- This should be used for cycling up and down by single lines. 18 | -- When the cursor line starts at the bottom or top, the index must be moved anyway. 19 | AnchorIndex 20 | deriving stock (Eq, Show, Generic) 21 | 22 | data MenuAction a = 23 | Continue 24 | | 25 | Render RenderAnchor 26 | | 27 | UpdatePrompt Prompt 28 | | 29 | UpdatePromptState PromptState 30 | | 31 | Quit (MenuResult a) 32 | deriving stock (Eq, Show, Functor) 33 | 34 | success :: 35 | a -> 36 | MenuAction a 37 | success = 38 | Quit . MenuResult.Success 39 | 40 | abort :: 41 | MenuAction a 42 | abort = 43 | Quit MenuResult.Aborted 44 | 45 | describe :: 46 | MenuAction a -> 47 | Text 48 | describe = \case 49 | Continue -> "Continue" 50 | Render a -> [exon|Render #{show a}|] 51 | UpdatePrompt p -> [exon|UpdatePrompt #{show p}|] 52 | UpdatePromptState p -> [exon|UpdatePromptState #{show p}|] 53 | Quit res -> [exon|Quit #{MenuResult.describe res}|] 54 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Class/Msgpack/Array.hs: -------------------------------------------------------------------------------- 1 | -- |Helper for encoding values to a heterogeneous MessagePack array. 2 | module Ribosome.Host.Class.Msgpack.Array where 3 | 4 | import Data.MessagePack (Object) 5 | import Data.Sequence ((|>)) 6 | 7 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode (toMsgpack)) 8 | 9 | newtype Acc = 10 | Acc { unAcc :: Seq Object } 11 | deriving stock (Eq, Show) 12 | 13 | -- |This class provides a variadic method for encoding MessagePack arrays. 14 | class MsgpackArray a where 15 | -- |Encode an arbitrary number of heterogeneously typed values to a single MessagePack array. 16 | -- This function is variadic, meaning that it takes an arbitrary number of arguments: 17 | -- 18 | -- >>> msgpackArray (5 :: Int) ("error" :: Text) (3.14 :: Double) :: Object 19 | -- ObjectArray [ObjectInt 5, ObjectString "error", ObjectFloat 3.14] 20 | -- 21 | -- This avoids the need to call 'Ribosome.toMsgpack' once for each element and then once more for the array. 22 | msgpackArray :: a 23 | 24 | instance MsgpackArray (Acc -> [Object]) where 25 | msgpackArray = 26 | toList . (.unAcc) 27 | 28 | instance MsgpackArray (Acc -> Object) where 29 | msgpackArray = 30 | toMsgpack . (.unAcc) 31 | 32 | instance MsgpackArray (a -> a) where 33 | msgpackArray = 34 | id 35 | 36 | instance ( 37 | MsgpackEncode a, 38 | MsgpackArray (Acc -> b) 39 | ) => MsgpackArray (Acc -> a -> b) where 40 | msgpackArray (Acc m) a = 41 | msgpackArray @(Acc -> b) (Acc (m |> toMsgpack a)) 42 | 43 | instance {-# overlappable #-} ( 44 | MsgpackEncode a, 45 | MsgpackArray (Acc -> b) 46 | ) => MsgpackArray (a -> b) where 47 | msgpackArray a = 48 | msgpackArray @(Acc -> b) (Acc [toMsgpack a]) 49 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/Request.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Data.Request where 2 | 3 | import Data.MessagePack (Object (ObjectArray)) 4 | import Exon (exon) 5 | 6 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode) 7 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode (toMsgpack)) 8 | 9 | newtype RpcMethod = 10 | RpcMethod { unRpcMethod :: Text } 11 | deriving stock (Eq, Show, Generic) 12 | deriving newtype (IsString, Ord, MsgpackDecode, MsgpackEncode, Semigroup, Monoid) 13 | 14 | newtype RequestId = 15 | RequestId { unRequestId :: Int64 } 16 | deriving stock (Eq, Show, Generic) 17 | deriving newtype (Num, Real, Enum, Integral, Ord, MsgpackDecode, MsgpackEncode) 18 | 19 | -- |The payload of an RPC request. 20 | data Request = 21 | Request { 22 | -- |The method, which is either the Neovim API function name or the internal identifier of a Ribosome handler. 23 | method :: RpcMethod, 24 | -- |The arguments. 25 | arguments :: [Object] 26 | } 27 | deriving stock (Eq, Show, Generic) 28 | 29 | instance MsgpackEncode Request where 30 | toMsgpack (Request m p) = 31 | ObjectArray [toMsgpack m, toMsgpack p] 32 | 33 | -- |An RPC request, which is a payload combined with a request ID. 34 | data TrackedRequest = 35 | TrackedRequest { 36 | -- |The ID is used to associate the response with the sender. 37 | id :: RequestId, 38 | -- |The payload. 39 | request :: Request 40 | } 41 | deriving stock (Eq, Show) 42 | 43 | formatReq :: Request -> Text 44 | formatReq (Request (RpcMethod method) args) = 45 | [exon|#{method} #{show args}|] 46 | 47 | formatTrackedReq :: TrackedRequest -> Text 48 | formatTrackedReq (TrackedRequest (RequestId i) req) = 49 | [exon|<#{show i}> #{formatReq req}|] 50 | -------------------------------------------------------------------------------- /packages/app/lib/Ribosome/App/Templates/ReadmeMd.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.App.Templates.ReadmeMd where 2 | 3 | import Exon (exon) 4 | 5 | import Ribosome.App.Data (Github (Github), ProjectName (ProjectName), GithubOrg (GithubOrg), GithubRepo (GithubRepo)) 6 | 7 | githubPlugin :: Github -> Text 8 | githubPlugin (Github (GithubOrg org) (GithubRepo repo)) = 9 | [exon| 10 | ```vim 11 | Plug '#{org}/#{repo}' 12 | ```|] 13 | 14 | nix :: Text 15 | nix = 16 | "[Nix package manager](https://nixos.org/learn.html)" 17 | 18 | onlyBuild :: Text 19 | onlyBuild = 20 | [exon|built using the #{nix}, which must be installed in the system.|] 21 | 22 | githubFetch :: ProjectName -> Github -> Text 23 | githubFetch (ProjectName name) _ = 24 | [exon|fetched or built on the first start. 25 | 26 | * If the #{nix} is available, the plugin will be fetched from the Nix 27 | cache (or built if the current commit isn't in the cache) 28 | * Otherwise it will be downloaded from Github's releases. 29 | * If the variable `g:#{name}_fetch_bin` is set to `1`, Nix is ignored and the binary is downloaded from Github 30 | regardless. 31 | |] 32 | 33 | readmeMd :: 34 | ProjectName -> 35 | Maybe Github -> 36 | Text 37 | readmeMd pn@(ProjectName name) github = 38 | [exon|# Intro 39 | *#{name}* is a Neovim plugin. 40 | 41 | # Install 42 | 43 | The plugin can be loaded by specifying the repo to a package manager like any other, for example by cloning it in a 44 | subdirectory of `'packpath'` or using one of the many plugin managers. 45 | #{foldMap githubPlugin github} 46 | 47 | Since the plugin is written in Haskell with the 48 | [Ribosome](https://hackage.haskell.org/package/ribosome/docs/Ribosome.html) framework, its executable has to be 49 | #{maybe onlyBuild (githubFetch pn) github} 50 | |] 51 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Data/RpcError.hs: -------------------------------------------------------------------------------- 1 | -- |The basic error type for the plugin host. 2 | module Ribosome.Host.Data.RpcError where 3 | 4 | import Data.MessagePack (Object) 5 | import qualified Data.Text as Text 6 | import Exon (exon) 7 | import Log (Severity (Error)) 8 | 9 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode) 10 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode) 11 | import Ribosome.Host.Class.Msgpack.Error (DecodeError, renderError) 12 | import Ribosome.Host.Data.Report (Report (Report), Reportable (toReport)) 13 | import Ribosome.Host.Data.Request (RpcMethod (RpcMethod)) 14 | 15 | -- |The basic error type for the plugin host, used by the listener, 'Rpc' and several other components. 16 | data RpcError = 17 | -- |An error that is supposed to be prevented by the implementation. 18 | Unexpected Text 19 | | 20 | -- |The Neovim API encountered a problem. 21 | Api RpcMethod [Object] Text 22 | | 23 | -- |A request was instructed to use the wrong decoder or the remote data was invalid. 24 | Decode DecodeError 25 | deriving stock (Eq, Show, Generic) 26 | deriving anyclass (MsgpackEncode, MsgpackDecode) 27 | 28 | instance IsString RpcError where 29 | fromString = 30 | Unexpected . toText 31 | 32 | instance Reportable RpcError where 33 | toReport = \case 34 | Unexpected e -> 35 | Report "Internal error" [e] Error 36 | Api (RpcMethod m) args e -> 37 | Report "Nvim API failure" [m, show args, e] Error 38 | Decode e -> 39 | toReport e 40 | 41 | -- |Extract an error message from an 'RpcError'. 42 | rpcError :: RpcError -> Text 43 | rpcError = \case 44 | Unexpected e -> e 45 | Api (RpcMethod m) args e -> [exon|#{m}: #{e}(#{Text.intercalate ", " (show <$> args)})|] 46 | Decode e -> renderError e 47 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Interpreter/Process/Socket.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Interpreter.Process.Socket where 2 | 3 | import Data.Serialize (Serialize) 4 | import qualified Network.Socket as Socket 5 | import Network.Socket (socketToHandle) 6 | import Path (toFilePath) 7 | import Polysemy.Process (Process, ProcessOptions, interpretProcessHandles) 8 | import Polysemy.Process.Data.ProcessError (ProcessError) 9 | import System.IO (Handle, IOMode (ReadWriteMode)) 10 | 11 | import Ribosome.Host.Data.BootError (BootError (BootError)) 12 | import Ribosome.Host.Data.NvimSocket (NvimSocket (NvimSocket)) 13 | import Ribosome.Host.Interpreter.Process.Cereal (interpretProcessInputCereal, interpretProcessOutputCereal) 14 | 15 | withSocket :: 16 | Members [Reader NvimSocket, Resource, Error BootError, Embed IO] r => 17 | (Handle -> Sem r a) -> 18 | Sem r a 19 | withSocket use = 20 | bracket acquire release \ socket -> 21 | use =<< embed (socketToHandle socket ReadWriteMode) 22 | where 23 | acquire = do 24 | NvimSocket path <- ask 25 | fromEither . first BootError =<< tryAny do 26 | socket <- Socket.socket Socket.AF_UNIX Socket.Stream 0 27 | socket <$ Socket.connect socket (Socket.SockAddrUnix (toFilePath path)) 28 | release = 29 | tryAny_ . Socket.close 30 | 31 | interpretProcessCerealSocket :: 32 | ∀ a r . 33 | Serialize a => 34 | Members [Reader NvimSocket, Error BootError, Log, Resource, Race, Async, Embed IO] r => 35 | ProcessOptions -> 36 | InterpreterFor (Process a (Either Text a) !! ProcessError) r 37 | interpretProcessCerealSocket options sem = 38 | withSocket \ handle -> 39 | interpretProcessOutputCereal $ 40 | interpretProcessInputCereal $ 41 | interpretProcessHandles options handle handle (raiseUnder2 sem) 42 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Api/Option.hs: -------------------------------------------------------------------------------- 1 | -- |API functions for Neovim options. 2 | module Ribosome.Api.Option where 3 | 4 | import Data.MessagePack (Object) 5 | import Data.Text (splitOn) 6 | import Exon (exon) 7 | 8 | import Ribosome.Host.Api.Data (vimGetOption, vimSetOption, nvimSetOption) 9 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode) 10 | import Ribosome.Host.Effect.Rpc (Rpc) 11 | import qualified Data.Text as Text 12 | 13 | -- |Append a string to a comma-separated option. 14 | optionCat :: 15 | Member Rpc r => 16 | Text -> 17 | Text -> 18 | Sem r () 19 | optionCat name extra = do 20 | current <- vimGetOption name 21 | vimSetOption name [exon|#{current},#{extra}|] 22 | 23 | -- |Append a string to the option @runtimepath@. 24 | rtpCat :: 25 | Member Rpc r => 26 | Text -> 27 | Sem r () 28 | rtpCat = 29 | optionCat "runtimepath" 30 | 31 | -- |Get a list of strings from a comma-separated option. 32 | optionList :: 33 | Member Rpc r => 34 | Text -> 35 | Sem r [Text] 36 | optionList name = do 37 | s <- vimGetOption name 38 | pure (splitOn "," s) 39 | 40 | -- |Set an option to a comma-separated list of strings. 41 | optionSetList :: 42 | Member Rpc r => 43 | Text -> 44 | [Text] -> 45 | Sem r () 46 | optionSetList name values = 47 | nvimSetOption name (Text.intercalate "," values) 48 | 49 | -- |Run an action with an option temporarily set to a value, then restore the old value. 50 | withOption :: 51 | ∀ a r b . 52 | Members [Rpc, Resource] r => 53 | MsgpackEncode a => 54 | Text -> 55 | a -> 56 | Sem r b -> 57 | Sem r b 58 | withOption name value = 59 | bracket setOpt reset . const 60 | where 61 | setOpt = 62 | vimGetOption @Object name <* vimSetOption name value 63 | reset = 64 | vimSetOption name 65 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Run.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Run where 2 | 3 | import Conc (interpretAtomic, interpretEventsChan) 4 | import Polysemy.Process (Process) 5 | 6 | import Ribosome.Host.Data.Event (Event) 7 | import Ribosome.Host.Data.HostConfig (LogConfig) 8 | import Ribosome.Host.Data.Report (LogReport) 9 | import Ribosome.Host.Data.Request (RequestId) 10 | import Ribosome.Host.Data.Response (Response) 11 | import Ribosome.Host.Data.RpcError (RpcError) 12 | import Ribosome.Host.Data.RpcMessage (RpcMessage) 13 | import Ribosome.Host.Effect.Reports (Reports) 14 | import Ribosome.Host.Effect.Responses (Responses) 15 | import Ribosome.Host.Effect.Rpc (Rpc) 16 | import Ribosome.Host.Effect.UserError (UserError) 17 | import Ribosome.Host.IOStack (IOStack) 18 | import Ribosome.Host.Interpreter.Log (interpretLogRpc, interpretReportLogRpc) 19 | import Ribosome.Host.Interpreter.Reports (interpretReports) 20 | import Ribosome.Host.Interpreter.Responses (interpretResponses) 21 | import Ribosome.Host.Interpreter.Rpc (interpretRpc) 22 | 23 | type RpcProcess = 24 | Process RpcMessage (Either Text RpcMessage) 25 | 26 | type RpcStack = 27 | [ 28 | Log, 29 | DataLog LogReport, 30 | Rpc !! RpcError, 31 | Responses RequestId Response !! RpcError, 32 | Events Event, 33 | EventConsumer Event, 34 | Reports 35 | ] 36 | 37 | type RpcDeps = 38 | [ 39 | RpcProcess, 40 | UserError 41 | ] 42 | 43 | interpretRpcStack :: 44 | Members IOStack r => 45 | Members RpcDeps r => 46 | Members [Log, Reader LogConfig] r => 47 | InterpretersFor RpcStack r 48 | interpretRpcStack = 49 | interpretReports . 50 | interpretEventsChan @Event . 51 | interpretResponses . 52 | interpretAtomic Nothing . 53 | interpretRpc . 54 | raiseUnder . 55 | interpretReportLogRpc . 56 | interpretLogRpc 57 | -------------------------------------------------------------------------------- /packages/host/test/Ribosome/Host/Test/AutocmdTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Host.Test.AutocmdTest where 2 | 3 | import Conc (interpretSync) 4 | import qualified Polysemy.Conc.Sync as Sync 5 | import Polysemy.Test (UnitTest, assertJust) 6 | import Polysemy.Time (Seconds (Seconds)) 7 | 8 | import Ribosome.Host.Api.Data (nvimCommand, nvimGetVar, nvimSetVar) 9 | import Ribosome.Host.Data.Execution (Execution (Async)) 10 | import Ribosome.Host.Data.Report (resumeReport) 11 | import Ribosome.Host.Data.RpcError (RpcError) 12 | import Ribosome.Host.Data.RpcHandler (Handler, RpcHandler) 13 | import qualified Ribosome.Host.Data.RpcType as AutocmdOptions 14 | import Ribosome.Host.Effect.Rpc (Rpc) 15 | import Ribosome.Host.Embed (embedNvim) 16 | import Ribosome.Host.Handler (rpcAutocmd) 17 | import Ribosome.Host.Unit.Run (runTest) 18 | 19 | var :: Text 20 | var = 21 | "test_var" 22 | 23 | au :: 24 | Members [Rpc !! RpcError, Sync ()] r => 25 | Handler r () 26 | au = do 27 | resumeReport (nvimSetVar var (12 :: Int)) 28 | void $ Sync.putWait (Seconds 5) () 29 | 30 | bn :: 31 | Members [Rpc !! RpcError, Sync ()] r => 32 | Handler r () 33 | bn = do 34 | resumeReport (nvimSetVar var (21 :: Int)) 35 | void $ Sync.putWait (Seconds 5) () 36 | 37 | handlers :: 38 | ∀ r . 39 | Members [Rpc !! RpcError, Sync ()] r => 40 | [RpcHandler r] 41 | handlers = 42 | [ 43 | rpcAutocmd "Au" Async "User" "Au" au, 44 | rpcAutocmd "Bn" Async "BufNew" def { AutocmdOptions.group = Just "test" } bn 45 | ] 46 | 47 | test_autocmd :: UnitTest 48 | test_autocmd = 49 | runTest $ interpretSync $ embedNvim handlers do 50 | nvimCommand "doautocmd User Au" 51 | Sync.takeWait (Seconds 5) 52 | assertJust @Int 12 =<< nvimGetVar var 53 | nvimCommand "new" 54 | Sync.takeWait (Seconds 5) 55 | assertJust @Int 21 =<< nvimGetVar var 56 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Data/Register.hs: -------------------------------------------------------------------------------- 1 | -- |Data types for register-related API functions. 2 | module Ribosome.Data.Register where 3 | 4 | import Data.Char (isAlpha, isNumber) 5 | import qualified Data.Text as Text 6 | import Exon (exon) 7 | import Prettyprinter (Pretty (pretty)) 8 | 9 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode (..)) 10 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode (..)) 11 | import Ribosome.Host.Class.Msgpack.Util (decodeString) 12 | 13 | -- |A Neovim register. 14 | data Register = 15 | Named Text 16 | | 17 | Numbered Text 18 | | 19 | Special Text 20 | | 21 | Empty 22 | deriving stock (Eq, Show, Generic) 23 | 24 | instance IsString Register where 25 | fromString = \case 26 | "" -> 27 | Empty 28 | [a] | isAlpha a -> 29 | Named (Text.singleton a) 30 | [a] | isNumber a -> 31 | Numbered (Text.singleton a) 32 | a -> 33 | Special (toText a) 34 | 35 | instance MsgpackDecode Register where 36 | fromMsgpack = 37 | decodeString 38 | 39 | instance MsgpackEncode Register where 40 | toMsgpack (Named a) = 41 | toMsgpack a 42 | toMsgpack (Numbered a) = 43 | toMsgpack a 44 | toMsgpack (Special a) = 45 | toMsgpack a 46 | toMsgpack Empty = 47 | toMsgpack ("" :: Text) 48 | 49 | -- |Render a register name by prefixing it with @"@. 50 | quoted :: Text -> Text 51 | quoted a = 52 | [exon|"#{a}|] 53 | 54 | -- |Render a register name as is usual for Neovim. 55 | registerRepr :: Register -> Text 56 | registerRepr = \case 57 | Named a -> 58 | quoted a 59 | Numbered a -> 60 | quoted a 61 | Special a -> 62 | quoted a 63 | Empty -> 64 | "" 65 | 66 | instance Pretty Register where 67 | pretty = \case 68 | Empty -> 69 | "no register" 70 | a -> 71 | pretty (registerRepr a) 72 | -------------------------------------------------------------------------------- /packages/ribosome/lib/Ribosome/Effect/Settings.hs: -------------------------------------------------------------------------------- 1 | -- |The effect 'Settings' abstracts Neovim variables 2 | module Ribosome.Effect.Settings where 3 | 4 | import Prelude hiding (get) 5 | 6 | import Ribosome.Data.Setting (Setting) 7 | import Ribosome.Data.SettingError (SettingError) 8 | import Ribosome.Host.Class.Msgpack.Decode (MsgpackDecode) 9 | import Ribosome.Host.Class.Msgpack.Encode (MsgpackEncode) 10 | 11 | -- |This effects abstracts Neovim variables with associated defaults. 12 | data Settings :: Effect where 13 | -- |Get the value of the setting's Neovim variable or return the default if it is undefined. 14 | Get :: MsgpackDecode a => Setting a -> Settings m a 15 | -- |Set the value of the setting's Neovim variable. 16 | Update :: MsgpackEncode a => Setting a -> a -> Settings m () 17 | 18 | makeSem_ ''Settings 19 | 20 | -- |Get the value of the setting's Neovim variable or return the default if it is undefined. 21 | get :: 22 | ∀ a r . 23 | MsgpackDecode a => 24 | Member Settings r => 25 | Setting a -> 26 | Sem r a 27 | 28 | -- |Set the value of the setting's Neovim variable. 29 | update :: 30 | ∀ a r . 31 | MsgpackEncode a => 32 | Member Settings r => 33 | Setting a -> 34 | a -> 35 | Sem r () 36 | 37 | -- |Get the setting's value or return the supplied fallback value if the Neovim variable is undefined and the setting 38 | -- has no default value. 39 | or :: 40 | MsgpackDecode a => 41 | Member (Settings !! SettingError) r => 42 | a -> 43 | Setting a -> 44 | Sem r a 45 | or a s = 46 | a 52 | Member (Settings !! SettingError) r => 53 | Setting a -> 54 | Sem r (Maybe a) 55 | maybe s = 56 | Nothing get s) 57 | -------------------------------------------------------------------------------- /packages/ribosome/test/Ribosome/Test/Wait.hs: -------------------------------------------------------------------------------- 1 | -- |Assertions that are made repeatedly until the succeed 2 | module Ribosome.Test.Wait where 3 | 4 | import qualified Conc 5 | import Conc (interpretAtomic) 6 | import Hedgehog.Internal.Property (Failure, failWith, liftTest, mkTest) 7 | import Polysemy.Test (Hedgehog, liftH) 8 | import qualified Polysemy.Time as Time 9 | import Polysemy.Time (MilliSeconds (MilliSeconds), Seconds (Seconds)) 10 | 11 | -- |Run an action and make an assertion about its result. 12 | -- Repeat on failure until the @timeout@ has been exceeded. 13 | -- 14 | -- Sleeps for @interval@ between attempts. 15 | assertWaitFor :: 16 | Monad m => 17 | HasCallStack => 18 | Members [Hedgehog m, Time t d, Race, Error Failure, Embed IO] r => 19 | TimeUnit t1 => 20 | TimeUnit t2 => 21 | t1 -> 22 | t2 -> 23 | Sem r a -> 24 | (a -> Sem r b) -> 25 | Sem r b 26 | assertWaitFor timeout interval acquire test = 27 | withFrozenCallStack do 28 | interpretAtomic Nothing do 29 | Conc.timeout_ timeoutError timeout spin 30 | where 31 | spin = do 32 | a <- raise acquire 33 | catch (raise (test a)) \ e -> do 34 | atomicPut (Just e) 35 | Time.sleep interval 36 | spin 37 | timeoutError = 38 | atomicGet >>= liftH . \case 39 | Just e -> liftTest (mkTest (Left e, mempty)) 40 | Nothing -> failWith Nothing "timed out before an assertion was made" 41 | 42 | -- |Run an action and make an assertion about its result. 43 | -- Repeat on failure for three seconds, every 100 milliseconds. 44 | assertWait :: 45 | Monad m => 46 | HasCallStack => 47 | Members [Hedgehog m, Time t d, Race, Error Failure, Embed IO] r => 48 | Sem r a -> 49 | (a -> Sem r b) -> 50 | Sem r b 51 | assertWait acquire test = 52 | withFrozenCallStack do 53 | assertWaitFor (Seconds 3) (MilliSeconds 100) acquire test 54 | -------------------------------------------------------------------------------- /packages/menu/test/Ribosome/Menu/Test/FilterTest.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Test.FilterTest where 2 | 3 | import Polysemy.Test (UnitTest, assertEq, runTestAuto, (===)) 4 | import qualified Streamly.Prelude as Stream 5 | import Zeugma (runTest) 6 | 7 | import Ribosome.Menu.Combinators (sortEntriesText) 8 | import Ribosome.Menu.Data.Filter (fuzzy) 9 | import qualified Ribosome.Menu.Data.MenuItem 10 | import Ribosome.Menu.Data.MenuItem (Items, simpleItems, simpleMenuItem) 11 | import Ribosome.Menu.Data.State (modal) 12 | import Ribosome.Menu.Effect.MenuFilter (FilterJob (Initial), menuFilter) 13 | import Ribosome.Menu.Interpreter.Menu (interpretMenuDeps, interpretMenus) 14 | import Ribosome.Menu.Interpreter.MenuFilter (interpretFilter) 15 | import Ribosome.Menu.Interpreter.MenuUi (interpretMenuUiNvimNull) 16 | import Ribosome.Menu.Items (currentEntries, currentEntriesText) 17 | import Ribosome.Menu.Loop (menuParams, withUi) 18 | import Ribosome.Test.Error (testError) 19 | import Ribosome.Test.Wait (assertWait) 20 | 21 | items :: Items () 22 | items = 23 | simpleItems [ 24 | "xaxbx", 25 | "xabc", 26 | "xaxbxcx", 27 | "ab" 28 | ] 29 | 30 | test_filterFuzzy :: UnitTest 31 | test_filterFuzzy = 32 | runTestAuto do 33 | r <- interpretFilter (menuFilter fuzzy "ab" (Initial items)) 34 | ["ab", "xabc", "xaxbx", "xaxbxcx"] === sortEntriesText r 35 | 36 | test_filterNoSort :: UnitTest 37 | test_filterNoSort = 38 | runTest do 39 | interpretFilter $ interpretMenuUiNvimNull $ interpretMenuDeps $ interpretMenus do 40 | testError $ withUi () $ menuParams (Stream.fromList noSortItems) (modal fuzzy) do 41 | assertWait currentEntries (assertEq (length noSortItems) . length) 42 | assertEq ((.text) <$> noSortItems) =<< currentEntriesText 43 | where 44 | noSortItems = item <$> [1 :: Int .. 120] 45 | 46 | item n = simpleMenuItem n (show n) 47 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Api/Autocmd.hs: -------------------------------------------------------------------------------- 1 | -- |Helpers for defining autocmds. 2 | module Ribosome.Host.Api.Autocmd where 3 | 4 | import Data.MessagePack (Object) 5 | import Prelude hiding (group) 6 | 7 | import Ribosome.Host.Api.Data (nvimCreateAugroup, nvimCreateAutocmd) 8 | import Ribosome.Host.Class.Msgpack.Encode (toMsgpack) 9 | import Ribosome.Host.Class.Msgpack.Map (msgpackMap) 10 | import Ribosome.Host.Data.RpcCall (RpcCall) 11 | import Ribosome.Host.Data.RpcType ( 12 | AutocmdBuffer (AutocmdBuffer), 13 | AutocmdEvents (AutocmdEvents), 14 | AutocmdGroup (AutocmdGroup), 15 | AutocmdId (AutocmdId), 16 | AutocmdOptions (..), 17 | AutocmdPatterns (AutocmdPatterns), 18 | ) 19 | 20 | -- |Create an @augroup@ if the first argument is 'Just', then call the second argument. 21 | -- 22 | -- The parameter of the callback is a 'Map' suitable to be passed to 'nvimCreateAutocmd', containing the group name. 23 | withAugroup :: Maybe AutocmdGroup -> (Map Text Object -> RpcCall a) -> RpcCall a 24 | withAugroup (Just (AutocmdGroup g)) f = 25 | nvimCreateAugroup g [("clear", toMsgpack False)] *> f [("group", toMsgpack g)] 26 | withAugroup Nothing f = 27 | f mempty 28 | 29 | -- |Create an autocmd. 30 | autocmd :: 31 | -- |Trigger events. 32 | AutocmdEvents -> 33 | -- |Options as defined for @:autocmd@. 34 | AutocmdOptions -> 35 | -- |The command to execute. 36 | Text -> 37 | RpcCall AutocmdId 38 | autocmd (AutocmdEvents events) AutocmdOptions {..} cmd = 39 | withAugroup group \ grp -> AutocmdId <$> nvimCreateAutocmd events (opts <> bufPat <> grp) 40 | where 41 | opts = 42 | msgpackMap ("command", cmd) ("once", once) ("nested", nested) 43 | bufPat = 44 | either bufOpt patternOpt target 45 | patternOpt (AutocmdPatterns pat) = 46 | [("pattern", toMsgpack pat)] 47 | bufOpt (AutocmdBuffer buf) = 48 | [("buffer", toMsgpack buf)] 49 | -------------------------------------------------------------------------------- /packages/test/lib/Ribosome/Test/Error.hs: -------------------------------------------------------------------------------- 1 | {-# options_haddock not-home #-} 2 | 3 | module Ribosome.Test.Error where 4 | 5 | import Polysemy.Test (TestError (TestError)) 6 | 7 | import Ribosome.Host.Data.Report (Reportable, mapReport, reportMessages) 8 | import Ribosome.Host.Data.RpcHandler (Handler) 9 | 10 | -- |Resume an effect and convert its error from @'Stop' err@ to @'Error' 'TestError'@. 11 | resumeTestError :: 12 | HasCallStack => 13 | ∀ eff err r . 14 | Show err => 15 | Members [eff !! err, Error TestError] r => 16 | InterpreterFor eff r 17 | resumeTestError = 18 | withFrozenCallStack do 19 | resumeHoistError (TestError . show) 20 | 21 | -- |Run a 'Handler', converting the @'Stop' 'Report'@ at its head to @'Error' 'TestError'@. 22 | testHandler :: 23 | HasCallStack => 24 | Member (Error TestError) r => 25 | Handler r a -> 26 | Sem r a 27 | testHandler = 28 | withFrozenCallStack do 29 | stopToErrorWith (TestError . reportMessages) 30 | 31 | -- |Run a 'Handler' in a new thread and return an action that waits for the thread to terminate when sequenced. 32 | -- Converts the @'Stop' 'Report'@ at its head to @'Error' 'TestError'@ when it is awaited. 33 | testHandlerAsync :: 34 | HasCallStack => 35 | Members [Error TestError, Async] r => 36 | Handler r a -> 37 | Sem r (Sem r a) 38 | testHandlerAsync h = 39 | withFrozenCallStack do 40 | thread <- async do 41 | runStop h 42 | pure do 43 | testHandler . stopEither =<< note (TestError "async handler didn't produce result") =<< await thread 44 | 45 | -- |Interpret @'Stop' err@ to @'Error' 'TestError'@ by using @err@'s instance of 'Reportable'. 46 | testError :: 47 | ∀ err r . 48 | HasCallStack => 49 | Reportable err => 50 | Member (Error TestError) r => 51 | InterpreterFor (Stop err) r 52 | testError ma = 53 | withFrozenCallStack do 54 | testHandler (mapReport (raiseUnder ma)) 55 | -------------------------------------------------------------------------------- /packages/host/lib/Ribosome/Host/Optparse.hs: -------------------------------------------------------------------------------- 1 | -- |Combinators for @optparse-applicative@. 2 | module Ribosome.Host.Optparse where 3 | 4 | import Exon (exon) 5 | import Log (Severity, parseSeverity) 6 | import Options.Applicative (ReadM, readerError) 7 | import Options.Applicative.Types (readerAsk) 8 | import Path (Abs, Dir, File, Path, SomeBase (Abs, Rel), parseSomeDir, parseSomeFile, ()) 9 | 10 | -- |Convert a path to absolute, using the first argument as base dir for relative paths. 11 | somePath :: 12 | Path Abs Dir -> 13 | SomeBase t -> 14 | Path Abs t 15 | somePath cwd = \case 16 | Abs p -> 17 | p 18 | Rel p -> 19 | cwd p 20 | 21 | -- |A logging severity option for @optparse-applicative@. 22 | severityOption :: ReadM Severity 23 | severityOption = do 24 | raw <- readerAsk 25 | maybe (readerError [exon|invalid log level: #{raw}|]) pure (parseSeverity (toText raw)) 26 | 27 | -- |Parse a path from a string in 'ReadM'. 28 | readPath :: 29 | String -> 30 | (String -> Either e (SomeBase t)) -> 31 | Path Abs Dir -> 32 | String -> 33 | ReadM (Path Abs t) 34 | readPath pathType parse cwd raw = 35 | either (const (readerError [exon|not a valid #{pathType} path: #{raw}|])) (pure . somePath cwd) (parse raw) 36 | 37 | -- |A path option for @optparse-applicative@. 38 | pathOption :: 39 | String -> 40 | (String -> Either e (SomeBase t)) -> 41 | Path Abs Dir -> 42 | ReadM (Path Abs t) 43 | pathOption pathType parse cwd = do 44 | raw <- readerAsk 45 | readPath pathType parse cwd raw 46 | 47 | -- |A directory path option for @optparse-applicative@. 48 | dirPathOption :: 49 | Path Abs Dir -> 50 | ReadM (Path Abs Dir) 51 | dirPathOption = 52 | pathOption "directory" parseSomeDir 53 | 54 | -- |A file path option for @optparse-applicative@. 55 | filePathOption :: 56 | Path Abs Dir -> 57 | ReadM (Path Abs File) 58 | filePathOption = 59 | pathOption "file" parseSomeFile 60 | -------------------------------------------------------------------------------- /packages/menu/lib/Ribosome/Menu/Stream/ParMap.hs: -------------------------------------------------------------------------------- 1 | module Ribosome.Menu.Stream.ParMap where 2 | 3 | import Control.Concurrent (getNumCapabilities) 4 | import qualified Streamly.Data.Fold as Fold 5 | import qualified Streamly.Internal.Data.Stream.IsStream.Exception as Stream 6 | import qualified Streamly.Prelude as Stream 7 | import Streamly.Prelude (IsStream) 8 | 9 | parConcatMap :: 10 | IsStream t => 11 | Int -> 12 | ([a] -> t IO b) -> 13 | t IO a -> 14 | t IO b 15 | parConcatMap chunks f s = 16 | Stream.bracket_ getNumCapabilities (const unit) \ threads -> 17 | Stream.maxThreads (max 1 (threads - 1)) $ 18 | Stream.fromParallel $ 19 | Stream.concatMapWith Stream.parallel (Stream.adapt . f) $ 20 | Stream.adapt $ 21 | Stream.chunksOf chunks Fold.toList s 22 | 23 | parMapChunks :: 24 | IsStream t => 25 | Int -> 26 | ([a] -> [b]) -> 27 | t IO a -> 28 | t IO b 29 | parMapChunks chunks f = 30 | parConcatMap chunks (Stream.fromList . f) 31 | 32 | parMap :: 33 | IsStream t => 34 | Int -> 35 | (a -> b) -> 36 | t IO a -> 37 | t IO b 38 | parMap chunks f = 39 | parConcatMap chunks (Stream.fromList . fmap f) 40 | 41 | parMapMaybe :: 42 | IsStream t => 43 | Int -> 44 | (a -> Maybe b) -> 45 | t IO a -> 46 | t IO b 47 | parMapMaybe chunks f = 48 | parConcatMap chunks (Stream.fromList . mapMaybe f) 49 | 50 | parMapChunksIO :: 51 | Int -> 52 | ([a] -> [b]) -> 53 | [a] -> 54 | IO [b] 55 | parMapChunksIO chunks f = 56 | Stream.toList . 57 | parMapChunks chunks f . 58 | Stream.fromList 59 | 60 | parMapIO :: 61 | Int -> 62 | (a -> b) -> 63 | [a] -> 64 | IO [b] 65 | parMapIO chunks f = 66 | Stream.toList . 67 | parMap chunks f . 68 | Stream.fromList 69 | 70 | parMapMaybeIO :: 71 | Int -> 72 | (a -> Maybe b) -> 73 | [a] -> 74 | IO [b] 75 | parMapMaybeIO chunks f = 76 | Stream.toList . 77 | parMapMaybe chunks f . 78 | Stream.fromList 79 | --------------------------------------------------------------------------------