├── .gitignore ├── .gitlab-ci.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── integration ├── __init__.py ├── _fixtures │ ├── conf │ │ └── tmux.conf │ └── screenshots │ │ ├── menu │ │ ├── initial │ │ │ └── screen │ │ ├── sub │ │ │ ├── filtered │ │ │ ├── final │ │ │ ├── initial │ │ │ ├── inverted │ │ │ ├── selected │ │ │ └── submenu │ │ └── wrap │ │ │ ├── 1 │ │ │ ├── 2 │ │ │ └── 3 │ │ └── prompt │ │ └── simple ├── _support │ ├── __init__.py │ ├── autocmd.py │ ├── base.py │ ├── command.py │ └── default_handler.py ├── api_spec.py ├── autocmd_spec.py ├── command_spec.py ├── default_handler_spec.py ├── function_spec.py ├── init_spec.py ├── menu_spec.py ├── msgpack_spec.py ├── prompt_spec.py ├── scratch_spec.py ├── settings_spec.py ├── tmux_spec.py └── vim_leave_spec.py ├── requirements.txt ├── ribosome ├── __init__.py ├── cli.py ├── components │ ├── __init__.py │ └── internal │ │ ├── __init__.py │ │ ├── config.py │ │ ├── mapping.py │ │ ├── prog.py │ │ └── update.py ├── compute │ ├── __init__.py │ ├── api.py │ ├── data.py │ ├── interpret.py │ ├── output.py │ ├── prog.py │ ├── program.py │ ├── ribosome.py │ ├── ribosome_api.py │ ├── run.py │ ├── tpe.py │ ├── tpe_data.py │ ├── wrap.py │ └── wrap_data.py ├── config │ ├── __init__.py │ ├── basic_config.py │ ├── component.py │ ├── config.py │ ├── resolve.py │ ├── resources.py │ ├── setting.py │ └── settings.py ├── data │ ├── __init__.py │ ├── mapping.py │ └── plugin_state.py ├── host.py ├── logging.py ├── nvim │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── command.py │ │ ├── data.py │ │ ├── exists.py │ │ ├── function.py │ │ ├── map.py │ │ ├── option.py │ │ ├── rpc.py │ │ ├── ui.py │ │ ├── util.py │ │ └── variable.py │ ├── io │ │ ├── __init__.py │ │ ├── api.py │ │ ├── compute.py │ │ ├── cons.py │ │ ├── data.py │ │ ├── state.py │ │ ├── tc.py │ │ └── trace.py │ ├── request.py │ ├── scratch.py │ └── syntax │ │ ├── __init__.py │ │ ├── cmd.py │ │ ├── expr.py │ │ └── syntax.py ├── options.py ├── process.py ├── rpc │ ├── __init__.py │ ├── api.py │ ├── arg_parser.py │ ├── args.py │ ├── comm.py │ ├── concurrency.py │ ├── data │ │ ├── __init__.py │ │ ├── nargs.py │ │ ├── prefix_style.py │ │ ├── rpc.py │ │ ├── rpc_method.py │ │ └── rpc_type.py │ ├── define.py │ ├── error.py │ ├── from_vim.py │ ├── handle_receive.py │ ├── io │ │ ├── __init__.py │ │ ├── connect.py │ │ ├── data.py │ │ └── start.py │ ├── nvim_api.py │ ├── receive.py │ ├── response.py │ ├── start.py │ ├── state.py │ ├── strict.py │ ├── to_plugin.py │ ├── to_vim.py │ └── uv │ │ └── __init__.py ├── test │ ├── __init__.py │ ├── config.py │ ├── integration │ │ ├── __init__.py │ │ ├── embed.py │ │ ├── external.py │ │ ├── klk.py │ │ ├── rpc.py │ │ ├── start.py │ │ └── tmux.py │ ├── klk │ │ ├── __init__.py │ │ ├── expectable.py │ │ ├── expectation.py │ │ └── matchers │ │ │ ├── __init__.py │ │ │ ├── buffer.py │ │ │ ├── command.py │ │ │ ├── nresult.py │ │ │ ├── prog.py │ │ │ ├── variable.py │ │ │ └── window.py │ ├── prog.py │ ├── request.py │ ├── rpc.py │ ├── run.py │ ├── unit.py │ └── unite.py └── util │ ├── __init__.py │ ├── callback.py │ ├── doc │ ├── __init__.py │ ├── data.py │ ├── format.py │ ├── generate.py │ ├── markdown.py │ ├── vim.py │ └── write.py │ ├── menu │ ├── __init__.py │ ├── auto │ │ ├── __init__.py │ │ ├── cmd.py │ │ ├── data.py │ │ └── run.py │ ├── codes.py │ ├── data.py │ ├── prompt │ │ ├── __init__.py │ │ ├── data.py │ │ ├── input.py │ │ ├── interrupt.py │ │ └── run.py │ └── run.py │ ├── persist.py │ ├── string.py │ └── tmux.py ├── scripts ├── doc.py └── generate.py ├── setup.py ├── test ├── __init__.py └── settings.py └── unit ├── __init__.py ├── _support ├── __init__.py └── spec.py ├── component_spec.py ├── dispatch_spec.py ├── doc_spec.py ├── klk_spec.py ├── logger_spec.py ├── mapping_spec.py ├── menu_spec.py ├── nvim_io_spec.py ├── persist_spec.py ├── prog_spec.py ├── request ├── __init__.py └── arg_validator_spec.py ├── rpc_spec.py ├── settings_spec.py └── update_state_spec.py /.gitignore: -------------------------------------------------------------------------------- 1 | unit/_temp 2 | integration/_temp 3 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | before_script: 2 | - pip install --upgrade -r requirements.txt 3 | 4 | unit: 5 | script: 6 | - klk unit 7 | 8 | integration: 9 | script: 10 | - klk integration 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: python 4 | env: 5 | - TRAVIS=true 6 | python: 7 | - "3.5.1" 8 | script: 9 | - spec unit 10 | - spec integration 11 | before_install: 12 | - sudo add-apt-repository -y ppa:neovim-ppa/unstable 13 | - sudo apt-get update 14 | - sudo apt-get install -qq -y libffi-dev libicu-dev llvm-dev exuberant-ctags neovim 15 | notifications: 16 | email: 17 | on_success: change 18 | on_failure: change 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### 8.12 4 | * Add option to run the test *neovim* instance in a tmux pane 5 | 6 | ### 8.5 7 | * Function decorator `ribosome.function` 8 | * Transient `ScratchMachine` that handles mappings in a scratch buffer 9 | * `NvimCmd` helper class allowing for delayed execution with unrestricted 10 | synchronicity type 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Torsten Schmits 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Intro 2 | 3 | **Note**: this project is deprecated and not maintained anymore. Try the new [Haskell version] instead! 4 | 5 | **ribosome** is a framework for building and testing **neovim** python remote plugins. 6 | It builds on the official [neovim-python] host, providing a more flexible and modular startup mechanism. 7 | 8 | Plugins built with **ribosome** can be conveniently managed by [chromatin]. 9 | 10 | # Definition 11 | 12 | *ribosome* plugins are defined declaratively. 13 | Configuration is contained in a single instance of class `Config`, which is located by the plugin host by importing the 14 | main module and analyzing the `__all__` attribute. 15 | The minimal requirement is the plugin name: 16 | 17 | ```python 18 | from ribosome.config import Config 19 | 20 | config = Config(name='counter') 21 | 22 | __all__ = ('config',) 23 | ``` 24 | 25 | # Starting 26 | 27 | The recommended way to launch a *ribosome* plugin is to use [chromatin]. 28 | 29 | At runtime, a plugin can be added with: 30 | 31 | ```vim 32 | Cram /path/to/package counter 33 | ``` 34 | 35 | To load the plugin automatically on start: 36 | 37 | ```vim 38 | let g:chromatin_rplugins = [ 39 | \ { 40 | \ 'name': 'counter', 41 | \ 'spec': '/path/to/package', 42 | \ } 43 | \ ] 44 | ``` 45 | 46 | The directory in `spec` has to be a package that *pip* can install, containing a `setup.py`. 47 | 48 | To test a plugin without chromatin, it can be started manually: 49 | 50 | ```vim 51 | call jobstart() 52 | ``` 53 | 54 | # Request Handlers 55 | 56 | 57 | # Components and Messages 58 | 59 | 60 | # Settings 61 | 62 | # Documentation 63 | 64 | [neovim-python]: https://github.com/neovim/python-client 65 | [chromatin]: https://github.com/tek/chromatin 66 | [Haskell version]: https://github.com/tek/ribosome 67 | -------------------------------------------------------------------------------- /integration/__init__.py: -------------------------------------------------------------------------------- 1 | import amino.test 2 | amino.test.setup(__file__) 3 | -------------------------------------------------------------------------------- /integration/_fixtures/conf/tmux.conf: -------------------------------------------------------------------------------- 1 | set -g default-command 'zsh -f' 2 | -------------------------------------------------------------------------------- /integration/_fixtures/screenshots/menu/initial/screen: -------------------------------------------------------------------------------- 1 | first 2 | second 3 | third 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 | mainmenu 1,1 All 36 | 37 | ~ 38 | ~ 39 | ~ 40 | ~ 41 | ~ 42 | ~ 43 | ~ 44 | ~ 45 | ~ 46 | ~ 47 | ~ 48 | ~ 49 | ~ 50 | ~ 51 | ~ 52 | ~ 53 | ~ 54 | ~ 55 | ~ 56 | ~ 57 | ~ 58 | ~ 59 | ~ 60 | ~ 61 | ~ 62 | ~ 63 | ~ 64 | ~ 65 | ~ 66 | ~ 67 | ~ 68 | ~ 69 | [No Name] 0,0-1 All -------------------------------------------------------------------------------- /integration/_fixtures/screenshots/menu/sub/filtered: -------------------------------------------------------------------------------- 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 | ~ 60 | ~ 61 | ~ 62 | ~ 63 | ~ 64 | ~ 65 | ~ 66 | [No Name] 0,0-1 All 67 | first 68 | third 69 | mainmenu 1,1 All 70 | ir -------------------------------------------------------------------------------- /integration/_fixtures/screenshots/menu/sub/final: -------------------------------------------------------------------------------- 1 | selected sub third 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 | ~ 60 | ~ 61 | ~ 62 | ~ 63 | ~ 64 | ~ 65 | ~ 66 | ~ 67 | ~ 68 | ~ 69 | [No Name] [+] 1,1 All -------------------------------------------------------------------------------- /integration/_fixtures/screenshots/menu/sub/initial: -------------------------------------------------------------------------------- 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 | ~ 60 | ~ 61 | ~ 62 | ~ 63 | ~ 64 | ~ 65 | [No Name] 0,0-1 All 66 | first 67 | second 68 | third 69 | mainmenu 1,1 All -------------------------------------------------------------------------------- /integration/_fixtures/screenshots/menu/sub/inverted: -------------------------------------------------------------------------------- 1 | first 2 | third 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 | mainmenu 1,1 All 36 | 37 | ~ 38 | ~ 39 | ~ 40 | ~ 41 | ~ 42 | ~ 43 | ~ 44 | ~ 45 | ~ 46 | ~ 47 | ~ 48 | ~ 49 | ~ 50 | ~ 51 | ~ 52 | ~ 53 | ~ 54 | ~ 55 | ~ 56 | ~ 57 | ~ 58 | ~ 59 | ~ 60 | ~ 61 | ~ 62 | ~ 63 | ~ 64 | ~ 65 | ~ 66 | ~ 67 | ~ 68 | ~ 69 | [No Name] 0,0-1 All 70 | ir -------------------------------------------------------------------------------- /integration/_fixtures/screenshots/menu/sub/selected: -------------------------------------------------------------------------------- 1 | first 2 | third 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 | mainmenu 1,1 All 36 | 37 | ~ 38 | ~ 39 | ~ 40 | ~ 41 | ~ 42 | ~ 43 | ~ 44 | ~ 45 | ~ 46 | ~ 47 | ~ 48 | ~ 49 | ~ 50 | ~ 51 | ~ 52 | ~ 53 | ~ 54 | ~ 55 | ~ 56 | ~ 57 | ~ 58 | ~ 59 | ~ 60 | ~ 61 | ~ 62 | ~ 63 | ~ 64 | ~ 65 | ~ 66 | ~ 67 | ~ 68 | ~ 69 | [No Name] 0,0-1 All 70 | ir -------------------------------------------------------------------------------- /integration/_fixtures/screenshots/menu/sub/submenu: -------------------------------------------------------------------------------- 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 | ~ 60 | ~ 61 | ~ 62 | ~ 63 | ~ 64 | ~ 65 | ~ 66 | ~ 67 | [No Name] 0,0-1 All 68 | sub third 69 | mainmenu 1,1 All -------------------------------------------------------------------------------- /integration/_fixtures/screenshots/menu/wrap/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 | ~ 60 | ~ 61 | ~ 62 | ~ 63 | ~ 64 | ~ 65 | [No Name] 0,0-1 All 66 | first 67 | second 68 | third 69 | mainmenu 1,1 All -------------------------------------------------------------------------------- /integration/_fixtures/screenshots/menu/wrap/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 | ~ 60 | ~ 61 | ~ 62 | ~ 63 | ~ 64 | ~ 65 | [No Name] 0,0-1 All 66 | first 67 | second 68 | third 69 | mainmenu 1,1 All -------------------------------------------------------------------------------- /integration/_fixtures/screenshots/menu/wrap/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 | ~ 60 | ~ 61 | ~ 62 | ~ 63 | ~ 64 | ~ 65 | [No Name] 0,0-1 All 66 | first 67 | second 68 | third 69 | mainmenu 1,1 All -------------------------------------------------------------------------------- /integration/_fixtures/screenshots/prompt/simple: -------------------------------------------------------------------------------- 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 | ~ 60 | ~ 61 | ~ 62 | ~ 63 | ~ 64 | ~ 65 | ~ 66 | ~ 67 | ~ 68 | ~ 69 | [No Name] 0,0-1 All 70 | aaaaa -------------------------------------------------------------------------------- /integration/_support/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tek/ribosome-py/8bd22e549ddff1ee893d6e3a0bfba123a09e96c6/integration/_support/__init__.py -------------------------------------------------------------------------------- /integration/_support/autocmd.py: -------------------------------------------------------------------------------- 1 | from amino import Map, List, Just 2 | 3 | from ribosome.compute.api import prog 4 | from ribosome.config.component import Component 5 | from ribosome.rpc.api import rpc 6 | from ribosome.config.config import Config, NoData 7 | from ribosome.nvim.api.variable import variable_set 8 | from ribosome.nvim.io.state import NS 9 | from ribosome.rpc.data.prefix_style import Plain 10 | 11 | val = 71 12 | 13 | 14 | @prog.unit 15 | def prog1() -> NS[NoData, None]: 16 | return NS.lift(variable_set('autocmd_success', val)) 17 | 18 | 19 | core: Component = Component.cons('core') 20 | 21 | 22 | autocmd_spec_config: Config = Config.cons( 23 | name='plug', 24 | components=Map(core=core), 25 | rpc=List( 26 | rpc.autocmd(prog1).conf(name=Just('vim_resized'), prefix=Plain()), 27 | ), 28 | internal_component=False, 29 | ) 30 | 31 | __all__ = ('autocmd_spec_config',) 32 | -------------------------------------------------------------------------------- /integration/_support/base.py: -------------------------------------------------------------------------------- 1 | from ribosome.test.integration.klk import PluginIntegrationKlkSpec 2 | 3 | 4 | class IntegrationSpecBase(PluginIntegrationKlkSpec): 5 | pass 6 | 7 | 8 | __all__ = ('IntegrationSpecBase',) 9 | -------------------------------------------------------------------------------- /integration/_support/command.py: -------------------------------------------------------------------------------- 1 | from amino import Map, List, Just, do, Do 2 | 3 | from ribosome.compute.api import prog 4 | from ribosome.config.component import Component 5 | from ribosome.rpc.api import rpc 6 | from ribosome.config.config import Config, NoData 7 | from ribosome.nvim.api.variable import variable_set 8 | from ribosome.nvim.io.state import NS 9 | 10 | val = 71 11 | 12 | 13 | @prog.unit 14 | @do(NS[NoData, None]) 15 | def prog1() -> Do: 16 | yield NS.lift(variable_set('command_success', val)) 17 | 18 | 19 | core: Component = Component.cons('core') 20 | 21 | 22 | command_spec_config: Config = Config.cons( 23 | name='plug', 24 | components=Map(core=core), 25 | rpc=List( 26 | rpc.write(prog1).conf(name=Just('prog_cmd')), 27 | ), 28 | internal_component=False, 29 | ) 30 | 31 | __all__ = ('command_spec_config',) 32 | -------------------------------------------------------------------------------- /integration/_support/default_handler.py: -------------------------------------------------------------------------------- 1 | from amino import Either, List, do, Do 2 | 3 | from ribosome.config.config import Config, NoData 4 | from ribosome.compute.api import prog 5 | from ribosome.rpc.api import rpc 6 | from ribosome.nvim.io.state import NS 7 | from ribosome.rpc.data.prefix_style import Plain 8 | 9 | class_name = 'ZeeKlass' 10 | 11 | 12 | @prog 13 | @do(NS[NoData, str]) 14 | def test_path() -> Do: 15 | yield NS.pure(Either.import_name('pkg', class_name).map(lambda a: a.__name__).value_or('failed')) 16 | 17 | 18 | default_handler_spec_config: Config = Config.cons( 19 | 'plug', 20 | rpc=List( 21 | rpc.write(test_path).conf(prefix=Plain(), sync=True), 22 | ) 23 | ) 24 | 25 | __all__ = ('default_handler_spec_config',) 26 | -------------------------------------------------------------------------------- /integration/api_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import k, Expectation, pending 2 | from kallikrein.matchers import contain 3 | from kallikrein.matchers.typed import have_type 4 | 5 | from amino import do, Do 6 | from amino.test.spec import SpecBase 7 | 8 | from ribosome.test.klk.expectable import kn 9 | from ribosome.nvim.io.compute import NvimIO 10 | from ribosome.nvim.api.rpc import channel_id 11 | from ribosome.nvim.api.variable import variable_set_prefixed, variable_prefixed_num 12 | 13 | 14 | # FIXME 15 | class ApiSpec(SpecBase): 16 | ''' 17 | channel id $channel_id 18 | set and get a prefixed variable $set_and_get_var 19 | ''' 20 | 21 | @pending 22 | def channel_id(self) -> Expectation: 23 | return kn(self.vim, channel_id).must(contain(have_type(int))) 24 | 25 | @pending 26 | def set_and_get_var(self) -> Expectation: 27 | name = 'varname' 28 | value = 5 29 | @do(NvimIO[int]) 30 | def go() -> Do: 31 | yield variable_set_prefixed(name, value) 32 | yield variable_prefixed_num(name) 33 | return kn(self.vim, go).must(contain(value)) 34 | 35 | 36 | __all__ = ('ApiSpec',) 37 | -------------------------------------------------------------------------------- /integration/autocmd_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import Expectation 2 | 3 | from amino.test.spec import SpecBase 4 | from amino import do, Do, List 5 | 6 | from ribosome.nvim.api.command import doautocmd 7 | from ribosome.test.integration.embed import TestConfig, plugin_test 8 | from ribosome.nvim.io.compute import NvimIO 9 | from ribosome.test.klk.matchers.variable import var_must_become 10 | 11 | from integration._support.autocmd import val, autocmd_spec_config 12 | 13 | 14 | test_config = TestConfig.cons(autocmd_spec_config, components=List('core')) 15 | 16 | 17 | @do(NvimIO[Expectation]) 18 | def autocmd_spec() -> Do: 19 | yield doautocmd('VimResized') 20 | yield var_must_become('autocmd_success', val) 21 | 22 | 23 | class AutocmdSpec(SpecBase): 24 | ''' 25 | execute handler when triggering an autocmd $autocmd 26 | ''' 27 | 28 | def autocmd(self) -> Expectation: 29 | return plugin_test(test_config, autocmd_spec) 30 | 31 | 32 | __all__ = ('AutocmdSpec',) 33 | -------------------------------------------------------------------------------- /integration/command_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import Expectation 2 | 3 | from amino import do, Do, List 4 | from amino.test.spec import SpecBase 5 | 6 | from ribosome.nvim.io.compute import NvimIO 7 | from ribosome.test.integration.embed import plugin_test, TestConfig 8 | from ribosome.test.klk.matchers.variable import var_must_become 9 | from ribosome.nvim.api.function import nvim_call_function 10 | 11 | from integration._support.command import command_spec_config, val 12 | 13 | 14 | @do(NvimIO[Expectation]) 15 | def command_spec() -> Do: 16 | yield nvim_call_function('PlugProgCmd') 17 | yield var_must_become('command_success', val) 18 | 19 | 20 | test_config = TestConfig.cons(command_spec_config, components=List('core')) 21 | 22 | 23 | class CommandSpec(SpecBase): 24 | ''' 25 | execute a command $command 26 | ''' 27 | 28 | def command(self) -> Expectation: 29 | return plugin_test(test_config, command_spec) 30 | 31 | 32 | __all__ = ('CommandSpec',) 33 | -------------------------------------------------------------------------------- /integration/default_handler_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import Expectation, k 2 | from kallikrein.matchers import contain 3 | from kallikrein.matchers.tuple import tupled 4 | 5 | from amino.test import temp_dir 6 | from amino.test.spec import SpecBase 7 | from amino import do, Do 8 | 9 | from ribosome.nvim.api.function import nvim_call_tpe, nvim_call_function, nvim_call_cons_strict 10 | from ribosome.test.integration.embed import TestConfig, plugin_test 11 | from ribosome.nvim.io.compute import NvimIO 12 | from ribosome.nvim.api.util import cons_checked_list 13 | 14 | from integration._support.default_handler import class_name, default_handler_spec_config 15 | 16 | 17 | test_config = TestConfig.cons(default_handler_spec_config) 18 | 19 | 20 | @do(NvimIO[Expectation]) 21 | def append_path_spec() -> Do: 22 | pkg = temp_dir('default_handler', 'pp', 'pkg') 23 | pp = pkg.parent 24 | file = pkg / '__init__.py' 25 | file.write_text(f'class {class_name}: pass') 26 | yield nvim_call_function('PlugAppendPythonPath', str(pp)) 27 | name = yield nvim_call_tpe(str, 'TestPath') 28 | path = yield nvim_call_cons_strict(cons_checked_list(str, lambda a: a), 'PlugShowPythonPath') 29 | return k((name, path)).must(tupled(2)((contain(class_name), contain(str(pp))))) 30 | 31 | 32 | class DefaultHandlerSpec(SpecBase): 33 | ''' 34 | append a directory to the plugin's `sys.path` $append_path 35 | ''' 36 | 37 | def append_path(self) -> Expectation: 38 | return plugin_test(test_config, append_path_spec) 39 | 40 | 41 | __all__ = ('DefaultHandlerSpec',) 42 | -------------------------------------------------------------------------------- /integration/function_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import k, Expectation 2 | 3 | from amino import List, do, Do 4 | from amino.test.spec import SpecBase 5 | 6 | from ribosome.config.config import Config, NoData 7 | from ribosome.compute.api import prog 8 | from ribosome.rpc.api import rpc 9 | from ribosome.nvim.io.state import NS 10 | from ribosome.test.integration.embed import TestConfig, plugin_test 11 | from ribosome.nvim.io.compute import NvimIO 12 | from ribosome.nvim.api.function import nvim_call_function 13 | 14 | val = 'result value' 15 | 16 | 17 | @prog 18 | @do(NS[NoData, str]) 19 | def test() -> Do: 20 | yield NS.pure(val) 21 | 22 | 23 | config: Config = Config.cons( 24 | 'function', 25 | rpc=List( 26 | rpc.write(test).conf(sync=True), 27 | ), 28 | internal_component=False, 29 | ) 30 | test_config = TestConfig.cons(config) 31 | 32 | 33 | @do(NvimIO[Expectation]) 34 | def function_spec() -> Do: 35 | r = yield nvim_call_function('FunctionTest') 36 | return k(r) == val 37 | 38 | 39 | class FunctionSpec(SpecBase): 40 | ''' 41 | call a function $function 42 | ''' 43 | 44 | def function(self) -> Expectation: 45 | return plugin_test(test_config, function_spec) 46 | 47 | 48 | __all__ = ('FunctionSpec',) 49 | -------------------------------------------------------------------------------- /integration/init_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import Expectation 2 | 3 | from amino import do, Do 4 | from amino.test.spec import SpecBase 5 | 6 | from ribosome.nvim.io.compute import NvimIO 7 | from ribosome.test.integration.embed import plugin_test, TestConfig 8 | from ribosome.nvim.api.variable import variable_set 9 | from ribosome.test.klk.matchers.variable import var_must_become 10 | from ribosome.config.config import Config 11 | from ribosome.compute.api import prog 12 | from ribosome.nvim.io.state import NS 13 | 14 | 15 | @prog 16 | @do(NS[None, None]) 17 | def init() -> Do: 18 | yield NS.lift(variable_set('init_success', 1)) 19 | 20 | 21 | init_spec_config: Config[None, None] = Config.cons( 22 | 'init', 23 | init=init, 24 | ) 25 | 26 | 27 | @do(NvimIO[Expectation]) 28 | def init_spec() -> Do: 29 | yield var_must_become('init_success', 1) 30 | 31 | 32 | test_config = TestConfig.cons(init_spec_config) 33 | 34 | 35 | class InitSpec(SpecBase): 36 | ''' 37 | execute the init program $init 38 | ''' 39 | 40 | def init(self) -> Expectation: 41 | return plugin_test(test_config, init_spec) 42 | 43 | 44 | __all__ = ('InitSpec',) 45 | -------------------------------------------------------------------------------- /integration/msgpack_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import k, Expectation 2 | from kallikrein.matchers.maybe import be_just 3 | 4 | from amino import List, do, Do, Dat, Nothing, Maybe 5 | from amino.test.spec import SpecBase 6 | 7 | from ribosome.config.config import Config, NoData 8 | from ribosome.compute.api import prog 9 | from ribosome.rpc.api import rpc 10 | from ribosome.nvim.io.state import NS 11 | from ribosome.test.integration.embed import TestConfig, plugin_test 12 | from ribosome.nvim.io.compute import NvimIO 13 | from ribosome.nvim.api.function import nvim_call_function 14 | from ribosome.nvim.io.api import N 15 | 16 | 17 | class A(Dat['A']): 18 | 19 | def __init__(self, i: int) -> None: 20 | self.i = i 21 | 22 | 23 | @prog 24 | @do(NS[NoData, A]) 25 | def error() -> Do: 26 | yield NS.pure(A(1)) 27 | 28 | 29 | config: Config = Config.cons( 30 | 'pack', 31 | rpc=List( 32 | rpc.write(error).conf(sync=True), 33 | ), 34 | internal_component=False, 35 | ) 36 | test_config = TestConfig.cons(config) 37 | 38 | 39 | @do(NvimIO[Expectation]) 40 | def pack_error_spec() -> Do: 41 | error = yield N.recover_failure( 42 | nvim_call_function('PackError').replace(Nothing), 43 | lambda a: N.pure(Maybe.optional(a)), 44 | ) 45 | return k(error).must(be_just) 46 | 47 | 48 | class UvSpec(SpecBase): 49 | ''' 50 | packing error $packing_error 51 | ''' 52 | 53 | def packing_error(self) -> Expectation: 54 | return plugin_test(test_config, pack_error_spec) 55 | 56 | 57 | __all__ = ('UvSpec',) 58 | -------------------------------------------------------------------------------- /integration/prompt_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import Expectation 2 | 3 | from chiasma.test.tmux_spec import tmux_spec_socket 4 | 5 | from amino.test.spec import SpecBase 6 | from amino import List, do, Do, Map, Dat 7 | from amino.logging import module_log 8 | 9 | from ribosome.test.integration.tmux import tmux_plugin_test, screenshot 10 | from ribosome.config.config import Config 11 | from ribosome.rpc.api import rpc 12 | from ribosome.compute.api import prog 13 | from ribosome.nvim.io.state import NS 14 | from ribosome.test.config import TestConfig 15 | from ribosome.nvim.io.compute import NvimIO 16 | from ribosome.util.menu.prompt.run import prompt 17 | from ribosome.util.menu.prompt.data import InputChar, InputState, PromptUnit 18 | from ribosome.nvim.api.ui import send_input 19 | from ribosome.nvim.io.api import N 20 | 21 | log = module_log() 22 | 23 | 24 | class PromptData(Dat['PromptData']): 25 | 26 | @staticmethod 27 | def cons( 28 | line: str='', 29 | ) -> 'PromptData': 30 | return PromptData( 31 | line, 32 | ) 33 | 34 | def __init__(self, line: str) -> None: 35 | self.line = line 36 | 37 | 38 | @do(NS[InputState[None, None], None]) 39 | def handle_input(keys: List[InputChar]) -> Do: 40 | yield NS.unit 41 | return PromptUnit() 42 | 43 | 44 | @prog 45 | @do(NS[None, None]) 46 | def write_prompt() -> Do: 47 | yield NS.lift(prompt(handle_input, None, False)) 48 | yield NS.unit 49 | 50 | 51 | config: Config[PromptData, None] = Config.cons( 52 | 'prompt', 53 | rpc=List(rpc.write(write_prompt)), 54 | state_ctor=PromptData.cons, 55 | ) 56 | vars = Map( 57 | prompt_tmux_socket=tmux_spec_socket, 58 | ) 59 | test_config = TestConfig.cons(config, vars=vars) 60 | 61 | 62 | @do(NvimIO[Expectation]) 63 | def prompt_spec() -> Do: 64 | yield send_input(':call PromptWritePrompt()') 65 | yield N.sleep(.1) 66 | yield send_input('a' * 5) 67 | yield N.sleep(.5) 68 | yield send_input('') 69 | yield screenshot('prompt', 'simple') 70 | 71 | 72 | class PromptSpec(SpecBase): 73 | ''' 74 | write a prompt $write_prompt 75 | ''' 76 | 77 | def write_prompt(self) -> Expectation: 78 | return tmux_plugin_test(test_config, prompt_spec) 79 | 80 | 81 | __all__ = ('PromptSpec',) 82 | -------------------------------------------------------------------------------- /integration/scratch_spec.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | from kallikrein import k, Expectation 4 | from kallikrein.matchers.length import have_length 5 | from kallikrein.matchers.lines import have_lines 6 | from kallikrein.matchers.tuple import tupled 7 | 8 | from amino import do, Do, List 9 | from amino.boolean import true 10 | from amino.test.spec import SpecBase 11 | 12 | from ribosome.compute.api import prog 13 | from ribosome.nvim.io.state import NS 14 | from ribosome.nvim.io.compute import NvimIO 15 | from ribosome.nvim.scratch import CreateScratchBufferOptions, show_in_scratch_buffer 16 | from ribosome.test.config import single_prog_config 17 | from ribosome.nvim.api.ui import buffer_content, buffers, windows, current_buffer 18 | from ribosome.nvim.api.data import Window, Buffer 19 | from ribosome.config.config import NoData 20 | from ribosome.test.integration.embed import TestConfig 21 | from ribosome.test.integration.external import external_state_test 22 | from ribosome.data.plugin_state import PS 23 | from ribosome.test.prog import request 24 | 25 | 26 | @prog.result 27 | @do(NS[NoData, None]) 28 | def create_scratch(lines: List[str], options: CreateScratchBufferOptions) -> Do: 29 | yield NS.lift(show_in_scratch_buffer(lines, options)) 30 | yield NS.unit 31 | 32 | 33 | config = single_prog_config(create_scratch, json=true) 34 | test_config = TestConfig.cons(config, components=List('main')) 35 | lines = List('1', '2', '3') 36 | 37 | 38 | @do(NvimIO[Tuple[List[Window], List[Buffer], List[str]]]) 39 | def ui_data() -> Do: 40 | wins = yield windows() 41 | bufs = yield buffers() 42 | buf = yield current_buffer() 43 | content = yield buffer_content(buf) 44 | return wins, bufs, content 45 | 46 | 47 | @do(NS[PS, Expectation]) 48 | def launch_spec() -> Do: 49 | yield request('create_scratch', lines, '{}') 50 | data = yield NS.lift(ui_data()) 51 | return k(data).must(tupled(3)((have_length(2), have_length(2), have_lines(lines)))) 52 | 53 | 54 | class ScratchSpec(SpecBase): 55 | ''' 56 | launch a scratch buffer $launch 57 | ''' 58 | 59 | def launch(self) -> Expectation: 60 | return external_state_test(test_config, launch_spec) 61 | 62 | 63 | __all__ = ('ScratchSpec',) 64 | -------------------------------------------------------------------------------- /integration/settings_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import Expectation 2 | 3 | from amino import do, Do 4 | from amino.test.spec import SpecBase 5 | 6 | from ribosome.nvim.api.variable import variable_set 7 | from ribosome.nvim.api.exists import command_once_defined 8 | from ribosome.test.klk.matchers.variable import var_must_become 9 | from ribosome.nvim.io.compute import NvimIO 10 | from ribosome.test.integration.embed import TestConfig, plugin_test 11 | from ribosome.nvim.api.function import nvim_call_function 12 | 13 | from test.settings import settings_spec_config 14 | 15 | 16 | @do(NvimIO[Expectation]) 17 | def settings_spec() -> Do: 18 | yield nvim_call_function('PlugCheck') 19 | yield var_must_become('counter', 21) 20 | 21 | 22 | @do(NvimIO[None]) 23 | def pre() -> Do: 24 | yield variable_set('counter', 7) 25 | yield variable_set('inc', 14) 26 | 27 | 28 | test_config = TestConfig.cons(settings_spec_config, pre=pre) 29 | 30 | 31 | class SettingsSpec(SpecBase): 32 | ''' 33 | update a setting $update 34 | ''' 35 | 36 | def update(self) -> Expectation: 37 | return plugin_test(test_config, settings_spec) 38 | 39 | 40 | __all__ = ('SettingsSpec',) 41 | -------------------------------------------------------------------------------- /integration/tmux_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import Expectation, k 2 | from kallikrein.matchers.either import be_right 3 | from kallikrein.matchers import contain 4 | 5 | from chiasma.test.tmux_spec import tmux_spec_socket 6 | from chiasma.util.pid import discover_pane_by_pid, child_pids 7 | 8 | from amino.test.spec import SpecBase 9 | from amino import do, Do, List, Map 10 | 11 | from ribosome.test.integration.tmux import tmux_plugin_test 12 | from ribosome.nvim.io.compute import NvimIO 13 | from ribosome.config.config import Config 14 | from ribosome.rpc.api import rpc 15 | from ribosome.nvim.api.rpc import nvim_pid 16 | from ribosome.nvim.io.state import NS 17 | from ribosome.compute.api import prog 18 | from ribosome.test.config import TestConfig 19 | from ribosome.nvim.api.function import nvim_call_tpe 20 | from ribosome.util.tmux import tmux_to_nvim 21 | 22 | 23 | @prog 24 | def vim_pid() -> NS[None, int]: 25 | return NS.lift(nvim_pid()) 26 | 27 | 28 | config: Config[None, None] = Config.cons( 29 | 'tmux', 30 | rpc=List(rpc.write(vim_pid)) 31 | ) 32 | vars = Map( 33 | tmux_tmux_socket=tmux_spec_socket, 34 | ) 35 | test_config = TestConfig.cons(config, vars=vars) 36 | 37 | 38 | @do(NvimIO[Expectation]) 39 | def vim_pid_spec() -> Do: 40 | pid = yield nvim_call_tpe(int, 'TmuxVimPid') 41 | pane = yield tmux_to_nvim(discover_pane_by_pid(pid)) 42 | return k(child_pids(pane.pid).attempt).must(be_right(contain(pid))) 43 | 44 | 45 | class TmuxSpec(SpecBase): 46 | ''' 47 | find the vim pid $find_vim_pid 48 | ''' 49 | 50 | def find_vim_pid(self) -> Expectation: 51 | return tmux_plugin_test(test_config, vim_pid_spec) 52 | 53 | 54 | __all__ = ('TmuxSpec',) 55 | -------------------------------------------------------------------------------- /integration/vim_leave_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import Expectation, k 2 | 3 | from amino.test.spec import SpecBase 4 | from amino import do, Do, List, Map, IO, Nil, Path 5 | from amino.test import temp_file 6 | from amino.logging import module_log 7 | 8 | from ribosome.test.integration.embed import TestConfig, plugin_test 9 | from ribosome.nvim.io.compute import NvimIO 10 | from ribosome.compute.api import prog 11 | from ribosome.nvim.io.state import NS 12 | from ribosome.config.basic_config import NoData 13 | from ribosome.config.config import Config 14 | from ribosome.rpc.api import rpc 15 | from ribosome.nvim.api.rpc import nvim_quit 16 | from ribosome.test.klk.expectation import await_k 17 | from ribosome.nvim.api.variable import variable_str 18 | from ribosome.nvim.io.api import N 19 | 20 | log = module_log() 21 | data = 'success' 22 | 23 | 24 | @prog 25 | @do(NS[NoData, None]) 26 | def vim_leave() -> Do: 27 | path_e = yield NS.lift(variable_str('status_file')) 28 | path = yield NS.e(path_e) 29 | yield NS.from_io(IO.delay(Path(path).write_text, data)) 30 | 31 | 32 | config: Config = Config.cons( 33 | name='auto', 34 | rpc=List( 35 | rpc.autocmd(vim_leave, sync=True), 36 | ), 37 | internal_component=False, 38 | ) 39 | 40 | 41 | status_file = temp_file('vim_leave', 'status') 42 | test_config = TestConfig.cons(config, vars=Map(status_file=str(status_file))) 43 | 44 | 45 | @do(NvimIO[Expectation]) 46 | def quit_file() -> Do: 47 | text = yield N.recover_failure(N.from_io(IO.file(status_file)), lambda r: N.pure(Nil)) 48 | return k(text) == List(data) 49 | 50 | 51 | @do(NvimIO[Expectation]) 52 | def leave_spec() -> Do: 53 | yield nvim_quit() 54 | yield await_k(quit_file) 55 | 56 | 57 | class AutocmdSpec(SpecBase): 58 | ''' 59 | synchronously call a program at exit $leave 60 | ''' 61 | 62 | def leave(self) -> Expectation: 63 | return plugin_test(test_config, leave_spec) 64 | 65 | 66 | __all__ = ('AutocmdSpec',) 67 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | amino~=13.0.1a4 2 | msgpack-python~=0.5.6 3 | chiasma~=0.1.0.a28 4 | kallikrein~=0.22.0a15 5 | -------------------------------------------------------------------------------- /ribosome/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path[:0] = os.environ.get('RIBOSOME_PYTHONPATH', '').split(':') 5 | 6 | from ribosome.nvim.api.data import NvimApi 7 | from ribosome.logging import ribo_log 8 | 9 | __all__ = ('in_vim', 'NvimApi', 'ribo_log') 10 | -------------------------------------------------------------------------------- /ribosome/cli.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import logging 3 | 4 | 5 | def remove_path(p: str) -> None: 6 | if p in sys.path: 7 | sys.path.remove(p) 8 | 9 | 10 | def stage1(log: logging.Logger) -> int: 11 | try: 12 | remove_path('') 13 | remove_path('.') 14 | from amino import Lists 15 | from ribosome.host import start_file 16 | log.debug(f'ribosome_start_plugin: {sys.argv}, {sys.path}') 17 | def no_args() -> int: 18 | log.error(f'ribosome_start_plugin: missing argument for plugin file') 19 | return 1 20 | return Lists.wrap(sys.argv).lift(1).cata(start_file, no_args) 21 | except Exception as e: 22 | log.caught_exception_error(f'starting plugin with {sys.argv}', e) 23 | return 1 24 | 25 | 26 | def start_plugin() -> int: 27 | from amino import with_log 28 | return with_log(stage1) 29 | 30 | 31 | __all__ = ('start_plugin',) 32 | -------------------------------------------------------------------------------- /ribosome/components/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /ribosome/components/internal/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /ribosome/components/internal/config.py: -------------------------------------------------------------------------------- 1 | from amino.boolean import true 2 | from amino import List, Just 3 | 4 | from ribosome.components.internal.prog import (program_log, set_log_level, update_state, update_component_state, 5 | state_data, rpc_triggers, poll, append_python_path, show_python_path, 6 | enable_components, mapping, internal_init, component_state_data, 7 | rpc_job_stderr) 8 | from ribosome.config.component import Component 9 | from ribosome.rpc.data.prefix_style import Full 10 | from ribosome.rpc.api import rpc 11 | 12 | internal: Component = Component.cons( 13 | 'internal', 14 | rpc=List( 15 | rpc.read(program_log).conf(prefix=Full(), sync=true), 16 | rpc.write(set_log_level).conf(prefix=Full()), 17 | rpc.write(update_state).conf(json=true), 18 | rpc.write(update_component_state).conf(json=true), 19 | rpc.read(state_data).conf(name=Just('state'), prefix=Full()), 20 | rpc.write(component_state_data).conf(name=Just('component_state'), prefix=Full()), 21 | rpc.read(rpc_triggers).conf(internal=true, sync=true, prefix=Full()), 22 | rpc.read(poll).conf(prefix=Full()), 23 | rpc.write(append_python_path).conf(prefix=Full()), 24 | rpc.read(show_python_path).conf(prefix=Full()), 25 | rpc.write(enable_components).conf(prefix=Full()), 26 | rpc.write(mapping).conf(name=Just('map'), prefix=Full()), 27 | rpc.write(internal_init).conf(prefix=Full()), 28 | rpc.read(rpc_job_stderr).conf(prefix=Full()), 29 | ), 30 | ) 31 | 32 | __all__ = ('internal',) 33 | -------------------------------------------------------------------------------- /ribosome/components/internal/mapping.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | from amino import do, curried, Do, __, _, Either 4 | from amino.lenses.lens import lens 5 | from amino.logging import module_log 6 | 7 | from ribosome.nvim.io.state import NS 8 | from ribosome.data.plugin_state import PluginState 9 | from ribosome.nvim.io.compute import NvimIO 10 | from ribosome.compute.program import Program 11 | from ribosome.config.component import Components 12 | from ribosome.nvim.api.command import nvim_command 13 | from ribosome.data.mapping import Mapping, MapMode 14 | 15 | log = module_log() 16 | 17 | 18 | def mapping_handler(mapping: Mapping) -> Callable[[Components], Either[str, Program]]: 19 | def mapping_handler(components: Components) -> Either[str, Program]: 20 | return components.all.find_map(__.mappings.lift(mapping)).to_either(f'no handler for {mapping}') 21 | return mapping_handler 22 | 23 | 24 | def mapping_cmd(plugin: str, mapping: Mapping, mode: MapMode) -> NvimIO[None]: 25 | buf = '' if mapping.buffer else '' 26 | keys = mapping.keys.replace('<', '') 27 | rhs = f''':call {plugin}Map('{mapping.ident}', '{keys}')''' 28 | return nvim_command( 29 | f'{mode.mnemonic}map', 30 | buf, 31 | '', 32 | mapping.keys, 33 | rhs, 34 | ) 35 | 36 | 37 | @do(NS[PluginState, None]) 38 | def activate_mapping(mapping: Mapping) -> Do: 39 | handler = yield NS.inspect_either(mapping_handler(mapping)).zoom(lens.components) 40 | yield NS.modify(__.append.active_mappings((mapping.ident, handler))) 41 | plugin = yield NS.inspect(_.camelcase_name) 42 | yield NS.lift(mapping.modes.traverse(curried(mapping_cmd)(plugin, mapping), NvimIO)) 43 | 44 | 45 | __all__ = ('activate_mapping',) 46 | -------------------------------------------------------------------------------- /ribosome/components/internal/update.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | 3 | from amino import do, Do, _, __, List, Either, Maybe 4 | from amino.lenses.lens import lens 5 | from amino.state import EitherState 6 | from amino.logging import module_log 7 | 8 | from ribosome.nvim.io.state import NS 9 | from ribosome.data.plugin_state import PluginState 10 | from ribosome.config.resolve import ComponentResolver 11 | from ribosome.config.component import Components 12 | from ribosome.nvim.api.command import nvim_command 13 | from ribosome.nvim.io.compute import NvimIO 14 | from ribosome.config import settings 15 | from ribosome.rpc.define import define_rpc, ActiveRpcTrigger, undef_command 16 | from ribosome.rpc.api import RpcProgram 17 | 18 | log = module_log() 19 | CC = TypeVar('CC') 20 | D = TypeVar('D') 21 | P = TypeVar('P') 22 | 23 | 24 | def programs(state: PluginState[D, CC]) -> List[RpcProgram]: 25 | cfg_handlers = state.rpc 26 | compo_handlers = state.components.all // _.rpc 27 | return compo_handlers + cfg_handlers 28 | 29 | 30 | @do(EitherState[str, PluginState[D, CC], None]) 31 | def update_components(requested: Either[str, List[str]]) -> Do: 32 | name = yield EitherState.inspect(_.basic.name) 33 | components_map = yield EitherState.inspect(_.comp.available) 34 | core_components = yield EitherState.inspect(_.basic.core_components) 35 | default_components = yield EitherState.inspect(_.basic.default_components) 36 | resolver = ComponentResolver(name, components_map, core_components, default_components, requested) 37 | components = yield EitherState.lift(resolver.run()) 38 | yield EitherState.modify(__.copy(components=Components.cons(components))) 39 | progs = yield EitherState.inspect(programs) 40 | yield EitherState.modify(lens.programs.set(progs)) 41 | 42 | 43 | @do(NvimIO[None]) 44 | def undef_trigger(trigger: ActiveRpcTrigger) -> Do: 45 | yield nvim_command(undef_command(trigger.method), trigger.prog.rpc_name) 46 | 47 | 48 | @do(NS[PluginState[D, CC], None]) 49 | def undef_triggers() -> Do: 50 | handlers = yield NS.inspect(_.rpc_triggers) 51 | yield NS.lift(handlers.traverse(undef_trigger, NvimIO)) 52 | 53 | 54 | @do(NS[PluginState[D, CC], None]) 55 | def def_triggers() -> Do: 56 | name = yield NS.inspect(_.basic.name) 57 | prefix = yield NS.inspect(_.basic.prefix) 58 | programs = yield NS.inspect(_.programs) 59 | handlers = yield NS.lift(define_rpc(programs, name, prefix)) 60 | yield NS.modify(lens.rpc_triggers.set(handlers)) 61 | 62 | 63 | @do(NS[PluginState[D, CC], None]) 64 | def init_rpc(requested_components: Maybe[List[str]]) -> Do: 65 | yield update_components(requested_components).nvim 66 | yield def_triggers() 67 | 68 | 69 | @do(NS[PluginState[D, CC], None]) 70 | def init_rpc_plugin() -> Do: 71 | requested_components = yield NS.lift(settings.components.value) 72 | yield init_rpc(requested_components) 73 | 74 | 75 | __all__ = ('init_rpc', 'undef_triggers', 'def_triggers', 'programs', 'init_rpc_plugin',) 76 | -------------------------------------------------------------------------------- /ribosome/compute/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = () 2 | -------------------------------------------------------------------------------- /ribosome/compute/data.py: -------------------------------------------------------------------------------- 1 | from typing import Generic, TypeVar 2 | 3 | from amino import ADT 4 | 5 | from ribosome.nvim.io.state import NS 6 | 7 | A = TypeVar('A') 8 | D = TypeVar('D') 9 | 10 | 11 | class Compilation(Generic[D, A], ADT['Compilation']): 12 | pass 13 | 14 | 15 | class CompilationSuccess(Generic[D, A], Compilation[D, A]): 16 | 17 | def __init__(self, prog: NS[D, A]) -> None: 18 | self.prog = prog 19 | 20 | 21 | class CompilationFailure(Generic[D, A], Compilation[D, A]): 22 | 23 | def __init__(self, error: str) -> None: 24 | self.error = error 25 | 26 | 27 | __all__ = ('Compilation', 'CompilationSuccess', 'CompilationFailure') 28 | -------------------------------------------------------------------------------- /ribosome/compute/prog.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Callable 2 | 3 | from amino import ADT, Maybe, Either 4 | from amino.tc.monad import Monad 5 | from amino.dat import ADTMeta 6 | from amino.func import CallByName, call_by_name 7 | from amino.tc.base import ImplicitsMeta, Implicits 8 | 9 | from ribosome.compute.wrap_data import ProgWrappers 10 | from ribosome.nvim.io.state import NS 11 | from ribosome.compute.output import ProgOutput 12 | 13 | A = TypeVar('A') 14 | B = TypeVar('B') 15 | D = TypeVar('D') 16 | M = TypeVar('M') 17 | P = TypeVar('P') 18 | S = TypeVar('S') 19 | R = TypeVar('R') 20 | 21 | 22 | class ProgMeta(ADTMeta, ImplicitsMeta): 23 | 24 | @property 25 | def unit(self) -> 'Prog[None]': 26 | return Prog.pure(None) 27 | 28 | 29 | class Prog(Generic[A], ADT['Prog[A]'], Implicits, implicits=True, auto=True, metaclass=ProgMeta): 30 | 31 | @staticmethod 32 | def from_maybe(fa: Maybe[A], error: CallByName) -> 'Prog[A]': 33 | return fa / Prog.pure | (lambda: Prog.error(error)) 34 | 35 | @staticmethod 36 | def m(fa: Maybe[A], error: CallByName) -> 'Prog[A]': 37 | return Prog.from_maybe(fa, error) 38 | 39 | @staticmethod 40 | def from_either(fa: Either[str, A]) -> 'Prog[A]': 41 | return fa.cata(Prog.error, Prog.pure) 42 | 43 | @staticmethod 44 | def e(fa: Either[B, A]) -> 'Prog[A]': 45 | return Prog.from_either(fa.lmap(str)) 46 | 47 | @staticmethod 48 | def pure(a: A) -> 'Prog[A]': 49 | return ProgPure(a) 50 | 51 | @staticmethod 52 | def error(error: CallByName) -> 'Prog[A]': 53 | return ProgError(call_by_name(error)) 54 | 55 | 56 | class ProgExec(Generic[A, B, S, R], Prog[B]): 57 | 58 | def __init__( 59 | self, 60 | name: str, 61 | code: NS[R, A], 62 | wrappers: ProgWrappers[S, R], 63 | output_type: ProgOutput, 64 | ) -> None: 65 | self.name = name 66 | self.code = code 67 | self.wrappers = wrappers 68 | self.output_type = output_type 69 | 70 | 71 | class ProgBind(Generic[A, B], Prog[B]): 72 | 73 | def __init__(self, fa: Prog[A], f: Callable[[A], Prog[B]]) -> None: 74 | self.fa = fa 75 | self.f = f 76 | 77 | 78 | class ProgPure(Generic[A], Prog[A]): 79 | 80 | def __init__(self, value: A) -> None: 81 | self.value = value 82 | 83 | 84 | class ProgError(Generic[A], Prog[A]): 85 | 86 | def __init__(self, msg: str) -> None: 87 | self.msg = msg 88 | 89 | 90 | class Monad_Prog(Monad, tpe=Prog): 91 | 92 | def pure(self, a: A) -> Prog[A]: 93 | return Prog.pure(a) 94 | 95 | def flat_map(self, fa: Prog[A], f: Callable[[A], Prog[B]]) -> Prog[B]: 96 | return ProgBind(fa, f) 97 | 98 | 99 | __all__ = ('Prog', 'ProgBind', 'ProgPure', 'ProgPure', 'ProgError') 100 | -------------------------------------------------------------------------------- /ribosome/compute/ribosome.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Generic, TypeVar, Type 3 | 4 | from lenses import UnboundLens 5 | 6 | from amino import Dat 7 | 8 | from ribosome.data.plugin_state import PluginState 9 | 10 | D = TypeVar('D') 11 | CC = TypeVar('CC') 12 | C = TypeVar('C') 13 | 14 | 15 | class Ribosome(Generic[D, CC, C], Dat['Ribosome[D, CC, C]']): 16 | 17 | def __init__( 18 | self, 19 | state: PluginState[D, CC], 20 | comp_type: Type[C], 21 | comp_lens: UnboundLens['Ribosome[D, CC, C]', 'Ribosome[D, CC, C]', C, C], 22 | ) -> None: 23 | self.state = state 24 | self.comp_type = comp_type 25 | self.comp_lens = comp_lens 26 | 27 | 28 | __all__ = ('Ribosome',) 29 | -------------------------------------------------------------------------------- /ribosome/compute/run.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Any, Generic 2 | 3 | from amino import List, _, __ 4 | from amino.do import do, Do 5 | from amino.case import Case 6 | 7 | from ribosome.nvim.io.state import NS 8 | from ribosome.compute.prog import Prog, ProgBind, ProgPure, ProgError, ProgExec 9 | from ribosome.compute.wrap_data import ProgWrappers 10 | from ribosome.data.plugin_state import PluginState 11 | from ribosome.compute.program import bind_program, Program 12 | from ribosome.compute.interpret import interpret 13 | 14 | A = TypeVar('A') 15 | B = TypeVar('B') 16 | D = TypeVar('D') 17 | R = TypeVar('R') 18 | CC = TypeVar('CC') 19 | 20 | 21 | @do(NS[PluginState[D, CC], A]) 22 | def transform_prog_state(st: NS[R, A], wrappers: ProgWrappers[PluginState[D, CC], R]) -> Do: 23 | yield st.transform_s(wrappers.get, wrappers.put) 24 | 25 | 26 | def log_prog(prog: ProgExec) -> NS[PluginState[D, CC], None]: 27 | return NS.pure(None) if prog.name in ('program_log', 'pure', 'lift') else NS.modify(__.log_prog(prog.name)) 28 | 29 | 30 | class eval_prog(Generic[A, B, R, D, CC], Case[Prog[A], NS[PluginState[D, CC], A]], alg=Prog): 31 | 32 | @do(NS[PluginState[D, CC], A]) 33 | def prog_exec(self, prog: ProgExec[B, A, R, Any]) -> Do: 34 | yield log_prog(prog) 35 | io_interpreter = yield NS.inspect(_.io_interpreter) 36 | output = yield transform_prog_state(prog.code, prog.wrappers) 37 | yield self(interpret(io_interpreter)(prog.output_type, output)) 38 | 39 | @do(NS[PluginState[D, CC], A]) 40 | def prog_bind(self, prog: ProgBind[Any, A]) -> Do: 41 | result = yield self(prog.fa) 42 | yield self(prog.f(result)) 43 | 44 | @do(NS[PluginState[D, CC], A]) 45 | def prog_pure(self, prog: ProgPure[A]) -> Do: 46 | yield NS.pure(prog.value) 47 | 48 | @do(NS[PluginState[D, CC], A]) 49 | def prog_error(self, prog: ProgError[A]) -> Do: 50 | yield NS.error(prog.msg) 51 | 52 | 53 | @do(NS[PluginState[D, CC], A]) 54 | def run_prog(program: Program[A], args: List[Any]) -> Do: 55 | yield eval_prog.match(bind_program(program, args)) 56 | 57 | 58 | __all__ = ('run_prog',) 59 | -------------------------------------------------------------------------------- /ribosome/compute/tpe.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, TypeVar 2 | 3 | from amino import Either, do, Do, Right 4 | from amino.util.tpe import first_type_arg, type_arg, normalize_generic_type, is_subclass, is_subclass 5 | from amino.tc.base import normalize_type 6 | 7 | from ribosome.config.resources import Resources 8 | from ribosome.config.component import ComponentData 9 | from ribosome.data.plugin_state import PluginState 10 | from ribosome.compute.tpe_data import (MainDataProgType, InternalMainDataProgType, PlainMainDataProgType, 11 | ComponentProgType, AffiliationProgType, PlainStateProgType, 12 | ResourcesStateProgType, StateProg, ProgType, UnknownProgType, RootProgType, 13 | RibosomeStateProgType) 14 | from ribosome.compute.ribosome import Ribosome 15 | from ribosome.compute.wrap import prog_wrappers 16 | from ribosome.compute.wrap_data import ProgWrappers 17 | from ribosome.nvim.io.state import NS 18 | from ribosome.rpc.args import ParamsSpec 19 | 20 | A = TypeVar('A') 21 | P = TypeVar('P') 22 | R = TypeVar('R') 23 | 24 | 25 | def main_data_prog(data_type: type) -> MainDataProgType: 26 | return ( 27 | InternalMainDataProgType() 28 | if is_subclass(data_type, PluginState) else 29 | PlainMainDataProgType() 30 | ) 31 | 32 | 33 | @do(Either[str, MainDataProgType]) 34 | def component_prog(affiliation_type: type) -> Do: 35 | tpe = yield first_type_arg(affiliation_type) 36 | component_data = yield type_arg(affiliation_type, 1) 37 | return ComponentProgType(main_data_prog(tpe), component_data) 38 | 39 | 40 | @do(Either[str, AffiliationProgType]) 41 | def affiliation_prog(affiliation_type: type) -> Do: 42 | yield ( 43 | component_prog(affiliation_type) 44 | if is_subclass(affiliation_type, ComponentData) else 45 | Right(RootProgType(main_data_prog(affiliation_type))) 46 | ) 47 | 48 | 49 | @do(Either[str, PlainStateProgType]) 50 | def plain_prog(plain_type: type) -> Do: 51 | affiliation = yield affiliation_prog(plain_type) 52 | return PlainStateProgType(affiliation) 53 | 54 | 55 | @do(Either[str, ResourcesStateProgType]) 56 | def resources_prog(resources_type: type) -> Do: 57 | tpe = yield first_type_arg(resources_type) 58 | affiliation = yield affiliation_prog(tpe) 59 | return ResourcesStateProgType(affiliation) 60 | 61 | 62 | @do(Either[str, RibosomeStateProgType]) 63 | def ribosome_prog(ribosome_type: type) -> Do: 64 | tpe = yield type_arg(ribosome_type, 2) 65 | return RibosomeStateProgType(tpe) 66 | 67 | 68 | @do(Either[str, StateProg]) 69 | def state_prog(state_type: type, return_type: type) -> Do: 70 | state_prog_type = yield ( 71 | resources_prog(state_type) 72 | if is_subclass(state_type, Resources) else 73 | ribosome_prog(state_type) 74 | if is_subclass(state_type, Ribosome) else 75 | plain_prog(state_type) 76 | ) 77 | return StateProg(state_prog_type, return_type) 78 | 79 | 80 | def analyse_prog_tpe(params: ParamsSpec) -> Either[str, ProgType]: 81 | return params.state_type.cata( 82 | lambda a: Right(UnknownProgType()), 83 | lambda a: state_prog(a, params.return_type), 84 | ) 85 | 86 | 87 | @do(Either[str, ProgWrappers]) 88 | def prog_type(func: Callable[[P], NS[R, A]], params_spec: ParamsSpec) -> Do: 89 | tpe = yield analyse_prog_tpe(params_spec) 90 | yield prog_wrappers.match(tpe) 91 | 92 | 93 | __all__ = ('analyse_prog_tpe', 'prog_type') 94 | -------------------------------------------------------------------------------- /ribosome/compute/tpe_data.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Type, Any 2 | 3 | from amino import ADT 4 | 5 | from ribosome.data.plugin_state import PluginState 6 | from ribosome.config.resources import Resources 7 | from ribosome.config.component import ComponentData 8 | from ribosome.compute.ribosome import Ribosome 9 | 10 | A = TypeVar('A') 11 | C = TypeVar('C') 12 | CC = TypeVar('CC') 13 | D = TypeVar('D') 14 | M = TypeVar('M') 15 | R = TypeVar('R') 16 | S = TypeVar('S') 17 | 18 | 19 | class MainDataProgType(Generic[M], ADT['MainDataProgType[M]']): 20 | pass 21 | 22 | 23 | class InternalMainDataProgType(Generic[D, CC], MainDataProgType[PluginState[D, CC]]): 24 | pass 25 | 26 | 27 | class PlainMainDataProgType(Generic[D], MainDataProgType[D]): 28 | pass 29 | 30 | 31 | class AffiliationProgType(Generic[M, C], ADT['AffiliationProgType[M, C]']): 32 | pass 33 | 34 | 35 | class RootProgType(Generic[M], AffiliationProgType[M, M]): 36 | 37 | def __init__(self, main: MainDataProgType[M]) -> None: 38 | self.main = main 39 | 40 | 41 | class ComponentProgType(Generic[M, C], AffiliationProgType[M, ComponentData[M, C]]): 42 | 43 | def __init__(self, main: MainDataProgType[M], comp: Type[C]) -> None: 44 | self.main = main 45 | self.comp = comp 46 | 47 | 48 | class StateProgType(Generic[M, C, R], ADT['StateProgType[M, C, R]']): 49 | pass 50 | 51 | 52 | class ResourcesStateProgType(Generic[M, C, D, CC], StateProgType[M, C, Resources[D, CC]]): 53 | 54 | def __init__(self, affiliation: AffiliationProgType[M, C]) -> None: 55 | self.affiliation = affiliation 56 | 57 | 58 | class PlainStateProgType(Generic[M, C], StateProgType[M, C, C]): 59 | 60 | def __init__(self, affiliation: AffiliationProgType[M, C]) -> None: 61 | self.affiliation = affiliation 62 | 63 | 64 | class RibosomeStateProgType(Generic[D, CC, C], StateProgType[PluginState[D, CC], C, Ribosome[D, CC, C]]): 65 | 66 | def __init__(self, comp: Type[C]) -> None: 67 | self.comp = comp 68 | 69 | 70 | class ProgType(Generic[M, C, R], ADT['ProgType[M, C, R]']): 71 | pass 72 | 73 | 74 | class UnknownProgType(Generic[M, C], ProgType[M, C, None]): 75 | pass 76 | 77 | 78 | class StateProg(Generic[M, C, R, A], ProgType[M, C, R]): 79 | 80 | def __init__(self, tpe: StateProgType[M, C, R], return_type: A) -> None: 81 | self.tpe = tpe 82 | self.return_type = return_type 83 | 84 | 85 | trivial_state_prog = StateProg(PlainStateProgType(RootProgType(PlainMainDataProgType())), Any) 86 | 87 | 88 | def ribo_state_prog(comp: Type[C]) -> StateProg[PluginState[D, CC], C, Ribosome[D, CC, C], Any]: 89 | return StateProg(RibosomeStateProgType(comp), Any) 90 | 91 | 92 | __all__ = ('MainDataProgType', 'InternalMainDataProgType', 'PlainMainDataProgType', 'AffiliationProgType', 93 | 'RootProgType', 'ComponentProgType', 'StateProgType', 'ResourcesStateProgType', 'PlainStateProgType', 94 | 'ProgType', 'UnknownProgType', 'StateProg', 'RibosomeProg', 'trivial_state_prog', 'ribo_state_prog') 95 | -------------------------------------------------------------------------------- /ribosome/compute/wrap_data.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, TypeVar, Generic 2 | 3 | from amino import Dat 4 | 5 | R = TypeVar('R') 6 | S = TypeVar('S') 7 | 8 | 9 | class ProgWrappers(Generic[R, S], Dat['TransWrappers[R, S]']): 10 | 11 | @staticmethod 12 | def id() -> 'ProgWrappers[R, R]': 13 | return ProgWrappers(lambda a: a, lambda a, b: b) 14 | 15 | def __init__(self, get: Callable[[R], S], put: Callable[[R, S], R]) -> None: 16 | self.get = get 17 | self.put = put 18 | 19 | 20 | __all__ = ('ProgWrappers',) 21 | -------------------------------------------------------------------------------- /ribosome/config/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /ribosome/config/basic_config.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, TypeVar, Generic 2 | 3 | from amino import Dat, List, Nil, Maybe 4 | 5 | D = TypeVar('D') 6 | 7 | 8 | class NoData(Dat['NoData']): 9 | pass 10 | 11 | 12 | class BasicConfig(Generic[D], Dat['BasicConfig[D]']): 13 | 14 | @staticmethod 15 | def cons( 16 | name: str, 17 | prefix: str=None, 18 | state_ctor: Callable[[], D]=None, 19 | core_components: List[str]=Nil, 20 | default_components: List[str]=Nil, 21 | internal_component: bool=True, 22 | settings_module: str=None, 23 | ) -> 'BasicConfig': 24 | return BasicConfig( 25 | name, 26 | prefix or name, 27 | state_ctor or NoData, 28 | core_components.cons('internal') if internal_component else core_components, 29 | default_components, 30 | Maybe.optional(settings_module), 31 | ) 32 | 33 | def __init__( 34 | self, 35 | name: str, 36 | prefix: str, 37 | state_ctor: Callable, 38 | core_components: List[str], 39 | default_components: List[str], 40 | settings_module: Maybe[str], 41 | ) -> None: 42 | self.name = name 43 | self.prefix = prefix 44 | self.state_ctor = state_ctor 45 | self.core_components = core_components 46 | self.default_components = default_components 47 | self.settings_module = settings_module 48 | 49 | 50 | __all__ = ('NoData', 'BasicConfig') 51 | -------------------------------------------------------------------------------- /ribosome/config/config.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, TypeVar, Generic, Optional, Any 2 | 3 | from amino import List, Nil, Map, __, Maybe 4 | from amino.dat import Dat 5 | 6 | from ribosome.config.component import Component 7 | from ribosome.compute.program import Program 8 | from ribosome.config.basic_config import NoData, BasicConfig 9 | from ribosome.components.internal.config import internal 10 | from ribosome.rpc.api import RpcProgram 11 | 12 | A = TypeVar('A') 13 | D = TypeVar('D') 14 | CC = TypeVar('CC') 15 | G = TypeVar('G') 16 | 17 | 18 | class Config(Generic[D, CC], Dat['Config[D, CC]']): 19 | 20 | @staticmethod 21 | def from_opt(data: Map) -> 'Config[D, CC]': 22 | return Config.cons(data.lift('name') | 'no name in json', data.lift('prefix') | None) 23 | 24 | @staticmethod 25 | def cons( 26 | name: str, 27 | prefix: Optional[str]=None, 28 | state_ctor: Callable[[], D]=None, 29 | components: Map[str, Component[Any, CC]]=Map(), 30 | rpc: List[RpcProgram]=Nil, 31 | core_components: List[str]=Nil, 32 | default_components: List[str]=Nil, 33 | init: Program=None, 34 | internal_component: bool=True, 35 | ) -> 'Config[D, CC]': 36 | basic = BasicConfig.cons( 37 | name, 38 | prefix, 39 | state_ctor, 40 | core_components, 41 | default_components, 42 | internal_component, 43 | ) 44 | return Config( 45 | basic, 46 | components + ('internal', internal) if internal_component else components, 47 | rpc, 48 | Maybe.optional(init), 49 | ) 50 | 51 | def __init__( 52 | self, 53 | basic: BasicConfig[D], 54 | components: Map[str, Component[Any, CC]], 55 | rpc: List[RpcProgram], 56 | init: Maybe[Program], 57 | ) -> None: 58 | self.basic = basic 59 | self.components = components 60 | self.rpc = rpc 61 | self.init = init 62 | 63 | 64 | __all__ = ('Config', 'NoData') 65 | -------------------------------------------------------------------------------- /ribosome/config/resolve.py: -------------------------------------------------------------------------------- 1 | from typing import Any, TypeVar 2 | 3 | from amino import Either, List, Left, do, Right, curried, Do, Map, Maybe 4 | from amino.mod import instance_from_module 5 | from amino.logging import module_log 6 | 7 | from ribosome.config.component import Component 8 | 9 | log = module_log() 10 | D = TypeVar('D') 11 | CC = TypeVar('CC') 12 | 13 | 14 | class ComponentResolver: 15 | 16 | def __init__( 17 | self, 18 | name: str, 19 | available_components: Map[str, Component[Any, CC]], 20 | core: List[str], 21 | default: List[str], 22 | requested: Maybe[List[str]], 23 | ) -> None: 24 | self.name = name 25 | self.available_components = available_components 26 | self.core = core 27 | self.default = default 28 | self.requested = requested 29 | 30 | def run(self) -> Either[str, List[Component]]: 31 | return self.components.traverse(self.create_components, Either) 32 | 33 | @property 34 | def components(self) -> List[str]: 35 | additional = self.requested | self.default 36 | components = self.core + additional 37 | log.debug(f'starting {self.name} with components {components}') 38 | return components 39 | 40 | def create_components(self, name: str) -> Either[str, List[Component]]: 41 | def report(errs: List[str]): 42 | msg = 'invalid {} component module "{}": {}' 43 | log.error(msg.format(self.name, name, errs)) 44 | return self.resolve_name(name).leffect(report) 45 | 46 | def resolve_name(self, name: str) -> Either[List[str], Component]: 47 | auto = f'{self.name}.components.{name}' 48 | return ( 49 | self.declared_component(name) 50 | .accum_error_f(lambda: self.component_from_exports(auto).lmap(List)) 51 | .accum_error_f(lambda: self.component_from_exports(name).lmap(List)) 52 | .flat_map(self.check_component(name)) 53 | ) 54 | 55 | def declared_component(self, name: str) -> Either[List[str], Component]: 56 | return ( 57 | self.available_components 58 | .lift(name) 59 | .to_either(List(f'no auto component defined for `{name}`')) 60 | ) 61 | 62 | @do(Either[str, Component]) 63 | def component_from_exports(self, mod: str) -> Do: 64 | mod = yield Either.import_module(mod) 65 | yield instance_from_module(mod, Component) 66 | 67 | @curried 68 | def check_component(self, name: str, comp: Component) -> Either[str, Component]: 69 | return ( 70 | Right(comp) 71 | if isinstance(comp, Component) else 72 | Left(List(f'invalid type for auto component: {comp}')) 73 | ) 74 | 75 | 76 | __all__ = ('ComponentResolver',) 77 | -------------------------------------------------------------------------------- /ribosome/config/resources.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic 2 | 3 | from amino.dat import Dat 4 | 5 | from ribosome.config.component import Components 6 | 7 | A = TypeVar('A') 8 | D = TypeVar('D') 9 | CC = TypeVar('CC') 10 | G = TypeVar('G') 11 | 12 | 13 | class Resources(Generic[D, CC], Dat['Resources[D, CC]']): 14 | 15 | def __init__(self, data: D, components: Components[CC]) -> None: 16 | self.data = data 17 | self.components = components 18 | 19 | 20 | __all__ = ('Resources',) 21 | -------------------------------------------------------------------------------- /ribosome/config/settings.py: -------------------------------------------------------------------------------- 1 | from amino.options import env_xdg_data_dir 2 | from amino import Either, Path, Right, Nil, Eval, Do 3 | from amino.do import do 4 | from amino.boolean import true 5 | 6 | from ribosome.nvim.io.compute import NvimIO 7 | from ribosome.config.setting import EvalSetting, list_setting, path_setting, str_setting, bool_setting 8 | from ribosome.nvim.io.api import N 9 | from ribosome.nvim.api.rpc import plugin_name 10 | 11 | 12 | @do(NvimIO[Either[str, Path]]) 13 | def state_dir_with_name() -> Do: 14 | plugin = yield plugin_name() 15 | base = yield state_dir.value_or_default() 16 | pro_name = yield proteome_main_name.value 17 | sess_name = yield ribosome_session_name.value 18 | path = sess_name.o(pro_name) / (lambda a: base / plugin / a) 19 | yield N.pure(path) 20 | 21 | 22 | state_dir_help = '''This directory is used to persist the plugin's current state.''' 23 | state_dir_base_default = env_xdg_data_dir.value / Path | (Path.home() / '.local' / 'share') 24 | proteome_name_help = 'If **proteome** is installed, the session name is obtained from the main project name.' 25 | session_name_help = 'A custom session name for the state dir can be specified.' 26 | components_help = '''The plugin can run an arbitrary set of sub-components that inherit the class `Component`. 27 | They receive all messages the core of the plugin processes and have access to the plugin state. 28 | They can define nvim request handlers with the decorator `@prog`. 29 | The entries of this setting can either be names of the builtin components or arbitrary python module paths that define 30 | custom components. 31 | Users can use this variable to inject programs into any plugin that can use the plugin's data. 32 | ''' 33 | 34 | components = list_setting('components', 'names or paths of active components', components_help, True, Right(Nil)) 35 | state_dir = path_setting('ribosome_state_dir', 'state persistence directory', state_dir_help, False, 36 | Right(state_dir_base_default)) 37 | proteome_main_name = str_setting('proteome_main_name', 'project name from protoeome', proteome_name_help, False) 38 | ribosome_session_name = str_setting('ribosome_session_name', 'project name from user var', session_name_help, False) 39 | project_state_dir = EvalSetting('project_state_dir', Eval.always(state_dir_with_name)) 40 | run_internal_init = bool_setting('run_internal_init', 'run internal initialization handler', '', True, Right(true)) 41 | 42 | 43 | __all__ = ('components', 'state_dir', 'proteome_main_name', 'ribosome_session_name', 'project_state_dir', 44 | 'run_internal_init') 45 | -------------------------------------------------------------------------------- /ribosome/data/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /ribosome/data/mapping.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Tuple 3 | 4 | from amino import Dat, Map, Boolean, ADT, List, Maybe, Lists 5 | from amino.boolean import false 6 | 7 | from ribosome.compute.program import Program 8 | 9 | 10 | class MapMode(ADT['MapMode']): 11 | 12 | @abc.abstractproperty 13 | def mnemonic(self) -> str: 14 | ... 15 | 16 | 17 | class mapmode: 18 | 19 | class Normal(MapMode): 20 | 21 | @property 22 | def mnemonic(self) -> str: 23 | return 'n' 24 | 25 | class Operator(MapMode): 26 | 27 | @property 28 | def mnemonic(self) -> str: 29 | return 'o' 30 | 31 | class Visual(MapMode): 32 | 33 | @property 34 | def mnemonic(self) -> str: 35 | return 'x' 36 | 37 | class Select(MapMode): 38 | 39 | @property 40 | def mnemonic(self) -> str: 41 | return 's' 42 | 43 | class VisualSelect(MapMode): 44 | 45 | @property 46 | def mnemonic(self) -> str: 47 | return 'v' 48 | 49 | class Insert(MapMode): 50 | 51 | @property 52 | def mnemonic(self) -> str: 53 | return 'i' 54 | 55 | class CommandLine(MapMode): 56 | 57 | @property 58 | def mnemonic(self) -> str: 59 | return 'c' 60 | 61 | class Terminal(MapMode): 62 | 63 | @property 64 | def mnemonic(self) -> str: 65 | return 't' 66 | 67 | class Language(MapMode): 68 | 69 | @property 70 | def mnemonic(self) -> str: 71 | return 'l' 72 | 73 | 74 | class Mapping(Dat['Mapping']): 75 | 76 | @staticmethod 77 | def cons(ident: str, keys: str, buffer: Boolean=false, modes: List[MapMode]=None) -> 'Mapping': 78 | modes1 = List(mapmode.Normal()) if modes is None else modes 79 | return Mapping(ident, keys, Boolean(buffer), modes1) 80 | 81 | def __init__(self, ident: str, keys: str, buffer: Boolean, modes: List[MapMode]) -> None: 82 | self.ident = ident 83 | self.keys = keys 84 | self.buffer = buffer 85 | self.modes = modes 86 | 87 | 88 | class Mappings(Dat['Mappings']): 89 | 90 | @staticmethod 91 | def cons(*mappings: Tuple[Mapping, Program]) -> 'Mappings': 92 | return Mappings(Map(Lists.wrap(mappings).map2(lambda m, p: (m.ident, (m, p))))) 93 | 94 | def __init__(self, mappings: Map[str, Tuple[Mapping, Program]]) -> None: 95 | self.mappings = mappings 96 | 97 | def lift(self, mapping: Mapping) -> Maybe[Program]: 98 | return self.mappings.lift(mapping.ident) 99 | 100 | 101 | __all__ = ('Mapping', 'Mappings') 102 | -------------------------------------------------------------------------------- /ribosome/nvim/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = () 2 | -------------------------------------------------------------------------------- /ribosome/nvim/api/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = () 2 | -------------------------------------------------------------------------------- /ribosome/nvim/api/command.py: -------------------------------------------------------------------------------- 1 | from typing import Any, TypeVar, Callable 2 | 3 | from ribosome.nvim.io.compute import NvimIO, NRParams 4 | 5 | from amino import List, do, Do, Either, Lists, Right, Left 6 | from amino.logging import module_log 7 | from ribosome.nvim.api.util import cons_split_lines, cons_checked_e 8 | from ribosome.nvim.io.api import N 9 | 10 | log = module_log() 11 | A = TypeVar('A') 12 | 13 | 14 | def nvim_command(cmd: str, *args: Any, params: NRParams=NRParams.cons(verbose=False, sync=False)) -> NvimIO[None]: 15 | arg_string = ' '.join(map(str, args)) 16 | arg_suffix = '' if len(args) == 0 else f' {arg_string}' 17 | silent = '' if params.verbose else 'silent! ' 18 | cmdline = f'{silent}{cmd}{arg_suffix}' 19 | log.debug1(lambda: f'nvim command `{cmdline}`') 20 | return N.write('nvim_command', cmdline, params=params) 21 | 22 | 23 | def nvim_sync_command(cmd: str, *args: Any, params: NRParams=NRParams.cons(verbose=False, sync=True)) -> NvimIO[None]: 24 | return nvim_command(cmd, *args, params=params) 25 | 26 | 27 | def nvim_command_output(cmd: str, *args: Any) -> NvimIO[None]: 28 | cmdline = ' '.join(map(str, (cmd,) + args)) 29 | return N.read_cons_strict('nvim_command_output', cons_split_lines, cmdline) 30 | 31 | 32 | def doautocmd(name: str, pattern: str='', params: NRParams=NRParams.cons(verbose=False, sync=False)) -> NvimIO[None]: 33 | return nvim_command(f'doautocmd', '', name, pattern, params=params) 34 | 35 | 36 | def runtime(path: str, params: NRParams=NRParams.cons(verbose=True, sync=False)) -> NvimIO[None]: 37 | return nvim_command('runtime!', f'{path}.vim', params=params) 38 | 39 | 40 | def defined_commands() -> NvimIO[List[str]]: 41 | return nvim_command_output('command') 42 | 43 | 44 | @do(NvimIO[str]) 45 | def defined_commands_str() -> Do: 46 | lines = yield defined_commands() 47 | return lines.join_lines 48 | 49 | 50 | @do(Either[str, None]) 51 | def atomic_error(cmdlines: List[str], error_raw: Any) -> Do: 52 | error = yield ( 53 | Right(error_raw) 54 | if isinstance(error_raw, list) else 55 | Left(f'invalid error structure for atomic call: {error_raw}') 56 | ) 57 | index, tpe, message = yield ( 58 | Lists.wrap(error) 59 | .lift_all(0, 1, 2) 60 | .to_either(f'too few elements in error structure for atomic call: {error}') 61 | ) 62 | offender = yield cmdlines.lift(index).to_either(f'invalid index in atomic call error: {error}') 63 | yield Left(f'error of type `{tpe}` in atomic call `{offender}`: {message}') 64 | 65 | 66 | def cons_atomic_result(cmdlines: List[str]) -> Callable[[list], Either[str, Any]]: 67 | @do(Either[str, Any]) 68 | def cons(raw: list) -> Do: 69 | result = Lists.wrap(raw) 70 | results, error = yield result.lift_all(0, 1).to_either_f(lambda: f'too few elements in atomic result: {result}') 71 | yield ( 72 | Right(results) 73 | if error is None 74 | else atomic_error(cmdlines, error) 75 | ) 76 | return cons 77 | 78 | 79 | def nvim_atomic_commands(cmdlines: List[str]) -> NvimIO[List[Any]]: 80 | cmds = cmdlines.map(lambda a: ['nvim_command', [a]]) 81 | return N.read_cons_strict('nvim_call_atomic', cons_checked_e(list, cons_atomic_result(cmdlines)), cmds) 82 | 83 | 84 | __all__ = ('nvim_command', 'nvim_command_output', 'doautocmd', 'runtime', 'nvim_sync_command', 'defined_commands', 85 | 'defined_commands_str', 'nvim_atomic_commands',) 86 | -------------------------------------------------------------------------------- /ribosome/nvim/api/exists.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, TypeVar, Any 2 | 3 | from amino import do, Do, Either, Right, Left 4 | 5 | from ribosome.nvim.io.compute import NvimIO, NRParams 6 | from ribosome.nvim.api.function import nvim_call_cons_strict, nvim_call_function 7 | from ribosome.nvim.api.util import nvimio_repeat_timeout, cons_checked_e 8 | from ribosome.nvim.api.command import nvim_command 9 | from ribosome.nvim.io.api import N 10 | 11 | A = TypeVar('A') 12 | 13 | 14 | def check_exists_bool(a: Any) -> Either[str, bool]: 15 | return Right(a != 0) if a in [0, 1, 2] else Left(f'nvim result is not an exists-boolean: {a}') 16 | 17 | 18 | cons_decode_exists_bool = cons_checked_e(int, check_exists_bool) 19 | 20 | 21 | def nvim_exists(target: str) -> NvimIO[bool]: 22 | return nvim_call_cons_strict(cons_decode_exists_bool, 'exists', target) 23 | 24 | 25 | def wait_until_valid( 26 | name: str, 27 | check: Callable[[str], NvimIO[bool]], 28 | timeout: float=30., 29 | interval: float=.01, 30 | desc: str='satify condition', 31 | ) -> NvimIO[None]: 32 | return nvimio_repeat_timeout( 33 | lambda: check(name), 34 | lambda a: a, 35 | f'{name} did not {desc} within {timeout} seconds', 36 | timeout, 37 | interval, 38 | ) 39 | 40 | 41 | def function_exists(name: str) -> NvimIO[bool]: 42 | return nvim_exists(f'*{name}') 43 | 44 | 45 | @do(NvimIO[bool]) 46 | def function_exists_not(name: str) -> Do: 47 | exists = yield function_exists(name) 48 | return not exists 49 | 50 | 51 | def command_exists(name: str) -> NvimIO[bool]: 52 | return nvim_exists(f':{name}') 53 | 54 | 55 | @do(NvimIO[bool]) 56 | def command_exists_not(name: str) -> Do: 57 | exists = yield command_exists(name) 58 | return not exists 59 | 60 | 61 | def wait_for_function(name: str, timeout: int=30, **kw: Any) -> NvimIO[None]: 62 | return wait_until_valid(name, function_exists, timeout=timeout, desc='appear', **kw) 63 | 64 | 65 | def wait_for_function_undef(name: str, timeout: int=30) -> NvimIO[None]: 66 | return wait_until_valid(name, function_exists_not, timeout) 67 | 68 | 69 | def wait_for_command(name: str, timeout: int=30) -> NvimIO[None]: 70 | return wait_until_valid(name, command_exists, timeout=timeout, desc='appear') 71 | 72 | 73 | @do(NvimIO[A]) 74 | def command_once_defined(name: str, *args: str, timeout: int=30, verbose: bool=False) -> Do: 75 | yield wait_for_command(name, timeout=timeout) 76 | yield nvim_command(name, *args, params=NRParams.cons(verbose=verbose, sync=False)) 77 | 78 | 79 | @do(NvimIO[A]) 80 | def call_once_defined(name: str, *args: str, timeout: int=10) -> Do: 81 | yield wait_for_function(name, timeout=timeout) 82 | yield nvim_call_function(name, *args) 83 | 84 | 85 | def wait_until_function_produces( 86 | target: Any, 87 | name: str, 88 | *args: str, 89 | timeout: int=10, 90 | interval: float=None, 91 | ) -> NvimIO[A]: 92 | return nvimio_repeat_timeout( 93 | lambda: N.recover_failure(nvim_call_function(name, *args), N.pure), 94 | lambda a: a == target, 95 | f'{name} did not produce `{target}` within {timeout} seconds', 96 | timeout, 97 | interval, 98 | ) 99 | 100 | 101 | __all__ = ('nvim_exists', 'wait_until_valid', 'function_exists', 'command_exists', 'wait_for_function', 102 | 'wait_for_command', 'command_once_defined', 'call_once_defined', 'function_exists_not', 103 | 'wait_for_function_undef', 'command_exists_not', 'wait_until_function_produces',) 104 | -------------------------------------------------------------------------------- /ribosome/nvim/api/function.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Type, TypeVar, Callable 2 | 3 | from ribosome.nvim.io.compute import NvimIO, NRParams 4 | 5 | from amino import Either, List 6 | from ribosome.nvim.api.command import nvim_command 7 | from ribosome.nvim.request import nvim_request 8 | from ribosome.nvim.io.api import N 9 | from ribosome.nvim.api.util import cons_json, cons_json_tpe 10 | 11 | A = TypeVar('A') 12 | 13 | 14 | def nvim_call_function(fun: str, *args: Any, params: NRParams=NRParams.cons(sync=True)) -> NvimIO[Any]: 15 | return nvim_request('nvim_call_function', fun, args, params=params) 16 | 17 | 18 | def nvim_call_tpe(tpe: Type[A], fun: str, *args: Any) -> NvimIO[A]: 19 | return N.read_tpe('nvim_call_function', tpe, fun, args) 20 | 21 | 22 | def nvim_call_cons( 23 | cons: Callable[[Any], NvimIO[Either[str, A]]], 24 | fun: str, 25 | *args: Any, 26 | params: NRParams=NRParams.cons(decode=True), 27 | ) -> NvimIO[A]: 28 | return N.read_cons('nvim_call_function', cons, fun, args, params=params) 29 | 30 | 31 | def nvim_call_cons_strict(cons: Callable[[Any], Either[str, A]], fun: str, *args: Any) -> NvimIO[A]: 32 | return N.read_cons_strict('nvim_call_function', cons, fun, args) 33 | 34 | 35 | # TODO check result; add `nvim_call_json_list` 36 | def nvim_call_json(fun: str, *args: Any) -> NvimIO[A]: 37 | return nvim_call_cons_strict(cons_json, fun, *args) 38 | 39 | 40 | def nvim_call_json_tpe(tpe: Type[A], fun: str, *args: Any) -> NvimIO[A]: 41 | return nvim_call_cons_strict(cons_json_tpe(tpe), fun, *args) 42 | 43 | 44 | def define_function(name: str, params: List[str], body: str) -> NvimIO[None]: 45 | return nvim_command(f'function!', f'{name}({params.join_comma})\n{body}\nendfunction') 46 | 47 | 48 | __all__ = ('nvim_call_function', 'nvim_call_tpe', 'nvim_call_cons_strict', 'define_function', 'nvim_call_json', 49 | 'nvim_call_json_tpe', 'nvim_call_cons',) 50 | -------------------------------------------------------------------------------- /ribosome/nvim/api/map.py: -------------------------------------------------------------------------------- 1 | from amino import do, Do 2 | 3 | from ribosome.nvim.io.compute import NvimIO 4 | from ribosome.nvim.api.command import nvim_command_output 5 | 6 | 7 | @do(NvimIO[str]) 8 | def show_mappings() -> Do: 9 | lines = yield nvim_command_output('map') 10 | return lines.join_lines 11 | 12 | 13 | __all__ = ('show_mappings',) 14 | -------------------------------------------------------------------------------- /ribosome/nvim/api/option.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, TypeVar, Any 2 | 3 | from amino import Either, List, do, Do, Right 4 | 5 | from ribosome.nvim.io.compute import NvimIO 6 | from ribosome.nvim.api.util import cons_decode_str, cons_decode_str_list, cons_decode_str_list_option, cons_int 7 | from ribosome.nvim.api.data import Buffer 8 | from ribosome.nvim.io.api import N 9 | 10 | A = TypeVar('A') 11 | B = TypeVar('B') 12 | 13 | 14 | def option(name: str, cons: Callable[[A], Either[str, B]]) -> NvimIO[B]: 15 | return N.read_cons_strict('nvim_get_option', cons, name) 16 | 17 | 18 | def option_str(name: str) -> NvimIO[str]: 19 | return option(name, cons_decode_str) 20 | 21 | 22 | def option_str_list(name: str) -> NvimIO[List[str]]: 23 | return option(name, cons_decode_str_list) 24 | 25 | 26 | def option_int(name: str) -> NvimIO[int]: 27 | return option(name, cons_int) 28 | 29 | 30 | def option_set(name: str, value: Any) -> NvimIO[None]: 31 | return N.write('nvim_set_option', name, value) 32 | 33 | 34 | @do(NvimIO[None]) 35 | def option_modify(name: str, cons: Callable[[A], Either[str, B]], modify: Callable[[B], Either[str, B]]) -> Do: 36 | value = yield option(name, cons) 37 | new = yield N.from_either(modify(value)) 38 | yield option_set(name, new) 39 | 40 | 41 | def option_cat(name: str, add: List[str]) -> NvimIO[None]: 42 | return option_modify(name, cons_decode_str_list_option, lambda a: Right((a + add.map(str)).mk_string(','))) 43 | 44 | 45 | def option_buffer_set(buffer: Buffer, name: str, value: Any) -> NvimIO[None]: 46 | return N.write('nvim_buf_set_option', buffer.data, name, value) 47 | 48 | 49 | def cat_rtp(dir: str) -> NvimIO[None]: 50 | return option_cat('runtimepath', List(dir)) 51 | 52 | 53 | __all__ = ('option', 'option_str', 'option_str_list', 'option_set', 'option_modify', 'option_cat', 'option_buffer_set', 54 | 'cat_rtp', 'option_int',) 55 | -------------------------------------------------------------------------------- /ribosome/nvim/api/rpc.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Any 2 | 3 | from amino import _, Either, Map, Left, Right, do, Do 4 | from amino.state import State 5 | 6 | from ribosome.nvim.io.compute import NvimIO, NvimIOSuspend, NvimIOPure 7 | from ribosome.nvim.io.api import N 8 | from ribosome.nvim.api.function import nvim_call_function, nvim_call_tpe 9 | from ribosome.nvim.api.command import nvim_command 10 | from ribosome import NvimApi 11 | 12 | 13 | def plugin_name() -> NvimIO[str]: 14 | return N.delay(_.name) 15 | 16 | 17 | def api_info() -> NvimIO[Tuple[int, dict]]: 18 | def cons(data: Any) -> Either[str, Tuple[int, Map[str, Any]]]: 19 | return ( 20 | Left(f'not a tuple: {data}') 21 | if not isinstance(data, (list, tuple)) else 22 | Left(f'invalid tuple size: {data}') 23 | if not len(data) == 2 else 24 | Left(f'channel is not an int: {data}') 25 | if not isinstance(data[0], int) else 26 | Left(f'metadata is not a dict: {data}') 27 | if not isinstance(data[1], dict) else 28 | Right(data).map2(lambda a, b: (a, Map(b))) 29 | ) 30 | return N.read_cons_strict('nvim_get_api_info', cons) 31 | 32 | 33 | @do(NvimIO[int]) 34 | def channel_id() -> Do: 35 | channel, metadata = yield api_info() 36 | return channel 37 | 38 | 39 | def rpcrequest(channel: int, method: str, *args: str) -> NvimIO[Any]: 40 | return nvim_call_function('rpcrequest', channel, method, args) 41 | 42 | 43 | @do(NvimIO[Any]) 44 | def rpcrequest_current(method: str, *args: str) -> Do: 45 | channel = yield channel_id() 46 | yield rpcrequest(channel, method, *args) 47 | 48 | 49 | def nvim_quit() -> NvimIO[None]: 50 | return nvim_command('qall!') 51 | 52 | 53 | def nvim_api() -> NvimIO[NvimApi]: 54 | return NvimIOSuspend.cons(State.get().map(NvimIOPure)) 55 | 56 | 57 | def nvim_pid() -> NvimIO[int]: 58 | return nvim_call_tpe(int, 'getpid') 59 | 60 | 61 | __all__ = ('plugin_name', 'api_info', 'channel_id', 'rpcrequest', 'rpcrequest_current', 'nvim_quit', 'nvim_api', 62 | 'nvim_pid',) 63 | -------------------------------------------------------------------------------- /ribosome/nvim/api/variable.py: -------------------------------------------------------------------------------- 1 | from numbers import Number 2 | from typing import Any, Callable, TypeVar 3 | 4 | from amino import Either, do, Do, Left, I 5 | from amino.logging import module_log 6 | 7 | from ribosome.nvim.io.compute import NvimIO 8 | from ribosome.nvim.api.rpc import plugin_name 9 | from ribosome.nvim.api.util import cons_decode_str, cons_checked_e 10 | from ribosome.nvim.api.data import Buffer 11 | from ribosome.nvim.request import nvim_nonfatal_request, data_cons_request_nonfatal, nvim_request 12 | from ribosome.nvim.io.api import N 13 | from ribosome.nvim.api.exists import wait_until_valid 14 | 15 | log = module_log() 16 | A = TypeVar('A') 17 | 18 | 19 | def variable_raw(name: str) -> NvimIO[Either[str, Any]]: 20 | return nvim_nonfatal_request('nvim_get_var', name) 21 | 22 | 23 | @do(NvimIO[Either[str, Any]]) 24 | def variable_prefixed_raw(name: str) -> Do: 25 | plug = yield plugin_name() 26 | yield variable_raw(f'{plug}_{name}') 27 | 28 | 29 | @do(NvimIO[Either[str, A]]) 30 | def variable(name: str, cons: Callable[[Any], Either[str, A]]) -> Do: 31 | log.debug(f'request variable `{name}`') 32 | def cons_e(result: Either[str, Any]) -> Either[str, A]: 33 | return result.cata(lambda err: Left(f'variable unset: {name}'), cons) 34 | value = yield data_cons_request_nonfatal('nvim_get_var', cons_e, name) 35 | log.debug(f'variable `{name}`: {value}') 36 | return value 37 | 38 | 39 | def variable_str(name: str) -> NvimIO[Either[str, str]]: 40 | return variable(name, cons_decode_str) 41 | 42 | 43 | def variable_num(name: str) -> NvimIO[Either[str, Number]]: 44 | return variable(name, cons_checked_e(Number, I)) 45 | 46 | 47 | @do(NvimIO[Either[str, A]]) 48 | def variable_prefixed(name: str, cons: Callable[[Any], Either[str, A]]) -> Do: 49 | plug = yield plugin_name() 50 | yield variable(f'{plug}_{name}', cons) 51 | 52 | 53 | def variable_prefixed_str(name: str) -> NvimIO[Either[str, str]]: 54 | return variable_prefixed(name, cons_decode_str) 55 | 56 | 57 | def variable_prefixed_num(name: str) -> NvimIO[Either[str, Number]]: 58 | return variable_prefixed(name, cons_checked_e(Number, I)) 59 | 60 | 61 | def variable_set(name: str, value: Any) -> NvimIO[None]: 62 | log.debug(f'setting variable `{name}` to `{value}`') 63 | return N.write('nvim_set_var', name, value) 64 | 65 | 66 | @do(NvimIO[None]) 67 | def variable_set_prefixed(name: str, value: Any) -> Do: 68 | plug = yield plugin_name() 69 | ident = f'{plug}_{name}' 70 | log.debug(f'setting variable `{ident}` to `{value}`') 71 | yield N.write('nvim_set_var', ident, value) 72 | 73 | 74 | def buffer_var_raw(buffer: Buffer, name: str) -> NvimIO[Any]: 75 | return nvim_request('nvim_buf_get_var', buffer.data, name) 76 | 77 | 78 | def var_equals(target: Any) -> Callable[[str], NvimIO[bool]]: 79 | @do(NvimIO[bool]) 80 | def var_equals(name: str) -> Do: 81 | actual = yield variable_raw(name) 82 | return actual.to_maybe.contains(target) 83 | return var_equals 84 | 85 | 86 | def var_becomes(name: str, value: Any, timeout: float=3, interval: float=.01) -> NvimIO[None]: 87 | return wait_until_valid(name, var_equals(value), timeout=timeout, interval=interval, desc=f'become {value}') 88 | 89 | 90 | @do(NvimIO[None]) 91 | def pvar_becomes(name: str, value: Any, timeout: float=3, interval: float=.01) -> Do: 92 | prefix = yield plugin_name() 93 | yield var_becomes(f'{prefix}_{name}', value, timeout=timeout, interval=interval) 94 | 95 | 96 | __all__ = ('variable_raw', 'variable_prefixed_raw', 'variable', 'variable_prefixed', 'variable_prefixed_str', 97 | 'variable_prefixed_num', 'variable_set', 'variable_set_prefixed', 'buffer_var_raw', 'variable_str', 98 | 'variable_num') 99 | -------------------------------------------------------------------------------- /ribosome/nvim/io/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = () 2 | -------------------------------------------------------------------------------- /ribosome/nvim/io/cons.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Callable, Any 2 | 3 | from amino import Either, Boolean, do, Do 4 | from amino.state import State 5 | from amino.boolean import true 6 | 7 | from ribosome.nvim.api.data import NvimApi 8 | from ribosome.nvim.io.compute import NvimIOSuspend, NvimIOPure, NvimIOError, NvimIO, NvimIORecover, lift_n_result 9 | from ribosome.nvim.io.data import NError, NFatal, NResult 10 | 11 | A = TypeVar('A') 12 | B = TypeVar('B') 13 | 14 | 15 | def nvimio_suspend(f: Callable[..., NvimIO[A]], *a: Any, **kw: Any) -> NvimIO[A]: 16 | return NvimIOSuspend.cons(State.inspect(lambda vim: f(vim, *a, **kw))) 17 | 18 | 19 | def nvimio_delay(f: Callable[..., A], *a: Any, **kw: Any) -> NvimIO[A]: 20 | return nvimio_suspend(lambda vim: NvimIOPure(f(vim, *a, **kw))) 21 | 22 | 23 | def nvimio_recover_error(fa: NvimIO[A], f: Callable[[NResult[A]], NvimIO[A]]) -> NvimIO[A]: 24 | return NvimIORecover(fa, f, Boolean.is_a(NError)) 25 | 26 | 27 | def nvimio_intercept(fa: NvimIO[A], f: Callable[[NResult[A]], NvimIO[B]]) -> NvimIO[B]: 28 | return NvimIORecover(fa, f, lambda r: true) 29 | 30 | 31 | def nvimio_recover_fatal(fa: NvimIO[A], f: Callable[[NResult[A]], NvimIO[A]]) -> NvimIO[A]: 32 | return NvimIORecover(fa, f, Boolean.is_a(NFatal)) 33 | 34 | 35 | def nvimio_recover_failure(fa: NvimIO[A], f: Callable[[NResult[A]], NvimIO[A]]) -> NvimIO[A]: 36 | return NvimIORecover(fa, f, Boolean.is_a((NError, NFatal))) 37 | 38 | 39 | def nvimio_ensure(fa: NvimIO[A], f: Callable[[NResult[A]], NvimIO[None]], pred: Callable[[NResult[A]], bool] 40 | ) -> NvimIO[A]: 41 | @do(NvimIO[A]) 42 | def effect(error: NResult[A]) -> Do: 43 | yield f(error) 44 | yield lift_n_result.match(error) 45 | return NvimIORecover(fa, effect, pred) 46 | 47 | 48 | def nvimio_wrap_either(f: Callable[[NvimApi], Either[B, A]]) -> NvimIO[A]: 49 | return nvimio_suspend(lambda v: f(v).cata(NvimIOError, NvimIOPure)) 50 | 51 | 52 | def nvimio_from_either(e: Either[str, A]) -> NvimIO[A]: 53 | return e.cata(NvimIOError, NvimIOPure) 54 | 55 | 56 | __all__ = ('nvimio_delay', 'nvimio_wrap_either', 'nvimio_from_either', 'nvimio_suspend', 'nvimio_recover_error', 57 | 'nvimio_recover_fatal', 'nvimio_recover_failure', 'nvimio_ensure', 'nvimio_intercept') 58 | -------------------------------------------------------------------------------- /ribosome/nvim/io/data.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Generic, TypeVar, Tuple 3 | from traceback import FrameSummary 4 | 5 | from amino import ADT, Either, Right, Left, Dat, Nil 6 | from amino.util.trace import cframe 7 | from amino.state import State 8 | 9 | from ribosome.nvim.io.trace import NvimIOException 10 | 11 | A = TypeVar('A') 12 | B = TypeVar('B') 13 | 14 | 15 | class NResult(Generic[A], ADT['NResult[A]']): 16 | 17 | @abc.abstractproperty 18 | def to_either(self) -> Either[Exception, A]: 19 | ... 20 | 21 | 22 | class NSuccess(Generic[A], NResult[A]): 23 | 24 | def __init__(self, value: A) -> None: 25 | self.value = value 26 | 27 | @property 28 | def to_either(self) -> Either[Exception, A]: 29 | return Right(self.value) 30 | 31 | 32 | class NError(Generic[A], NResult[A]): 33 | 34 | def __init__(self, error: str) -> None: 35 | self.error = error 36 | 37 | @property 38 | def to_either(self) -> Either[Exception, A]: 39 | return Left(Exception(self.error)) 40 | 41 | 42 | class NFatal(Generic[A], NResult[A]): 43 | 44 | def __init__(self, exception: Exception) -> None: 45 | self.exception = exception 46 | 47 | @property 48 | def to_either(self) -> Either[Exception, A]: 49 | return Left(self.exception) 50 | 51 | 52 | class Thunk(Generic[A, B], Dat['Thunk[A, B]']): 53 | 54 | @staticmethod 55 | def cons(thunk: State[A, B], frame: FrameSummary=None) -> 'Thunk[A, B]': 56 | return Thunk(thunk, frame or cframe()) 57 | 58 | def __init__(self, thunk: State[A, B], frame: FrameSummary) -> None: 59 | self.thunk = thunk 60 | self.frame = frame 61 | 62 | 63 | def eval_thunk(resource: A, thunk: Thunk[A, B]) -> Tuple[A, B]: 64 | try: 65 | return thunk.thunk.run(resource).value 66 | except NvimIOException as e: 67 | raise e 68 | except Exception as e: 69 | raise NvimIOException('', Nil, e, thunk.frame) 70 | 71 | 72 | __all__ = ('NResult', 'NSuccess', 'NError', 'NFatal', 'Thunk', 'eval_thunk') 73 | -------------------------------------------------------------------------------- /ribosome/nvim/io/tc.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Callable 2 | 3 | from amino.tc.monad import Monad 4 | from amino.tc.base import ImplicitInstances, tc_prop 5 | from amino.lazy import lazy 6 | from amino import Map 7 | from amino.tc.monoid import Monoid 8 | 9 | from ribosome.nvim.io.compute import NvimIO, flat_map_nvim_io, NvimIOPure 10 | 11 | A = TypeVar('A') 12 | B = TypeVar('B') 13 | 14 | 15 | class NvimIOInstances(ImplicitInstances): 16 | 17 | @lazy 18 | def _instances(self) -> Map: 19 | return Map({ 20 | Monad: NvimIOMonad(), 21 | Monoid: NvimIOMonoid(), 22 | }) 23 | 24 | 25 | class NvimIOMonad(Monad): 26 | 27 | def pure(self, a: A) -> NvimIO[A]: 28 | return NvimIOPure(a) 29 | 30 | def flat_map(self, fa: NvimIO[A], f: Callable[[A], NvimIO[B]]) -> NvimIO[B]: 31 | return flat_map_nvim_io(f)(fa) 32 | 33 | 34 | class NvimIOMonoid(Monoid): 35 | 36 | @tc_prop 37 | def empty(self) -> NvimIO[None]: 38 | return NvimIOPure(None) 39 | 40 | def combine(self, a: NvimIO[A], b: NvimIO[A]) -> NvimIO[A]: 41 | return a.flat_map(b) 42 | 43 | 44 | __all__ = ('NvimIOInstances', 'NvimIOMonad', 'NvimIOMonoid',) 45 | -------------------------------------------------------------------------------- /ribosome/nvim/io/trace.py: -------------------------------------------------------------------------------- 1 | from amino.io import IOExceptionBase 2 | from amino import Maybe, List, Just 3 | from amino.util.trace import default_internal_packages 4 | 5 | 6 | class NvimIOException(IOExceptionBase): 7 | 8 | @property 9 | def desc(self) -> str: 10 | return 'NvimIO exception' 11 | 12 | @property 13 | def internal_packages(self) -> Maybe[List[str]]: 14 | return Just(default_internal_packages.cons('ribosome.nvim')) 15 | 16 | 17 | __all__ = ('NvimIOException',) 18 | -------------------------------------------------------------------------------- /ribosome/nvim/request.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Any, Type, Callable 2 | 3 | from amino import Try, Lists, Either, do, Do 4 | 5 | from msgpack import ExtType 6 | from amino.util.string import decode as decode_data 7 | 8 | from ribosome.nvim.io.compute import NvimIO, NvimIOPure, NvimIORequest, NRParams 9 | from ribosome.nvim.io.trace import NvimIOException 10 | from ribosome.nvim.io.compute import NvimIOError 11 | from ribosome.nvim.io.cons import nvimio_recover_fatal, nvimio_from_either 12 | 13 | A = TypeVar('A') 14 | 15 | 16 | def nvim_error_msg(exc: Exception) -> str: 17 | return Try(lambda: decode_data(exc.args[0])) | str(exc) 18 | 19 | 20 | def nvim_request_error(name: str, args: tuple, desc: str, error: Any) -> NvimIO[A]: 21 | msg = (nvim_error_msg(error.cause) if isinstance(error, NvimIOException) else str(error)) 22 | return NvimIOError(f'{desc} in nvim request `{name}({Lists.wrap(args).join_comma})`: {msg}') 23 | 24 | 25 | @decode_data.register(ExtType) 26 | def decode_ext_type(a: ExtType) -> ExtType: 27 | return a 28 | 29 | 30 | @do(NvimIO[Either[str, Any]]) 31 | def nvim_nonfatal_request(name: str, *args: Any, params: NRParams=NRParams.cons()) -> Do: 32 | request: NvimIORequest[Any] = NvimIORequest(name, Lists.wrap(args), params) 33 | value = yield nvimio_recover_fatal(request, lambda a: nvim_request_error(name, args, 'fatal error', a)) 34 | yield ( 35 | nvimio_from_either(Try(value.map, decode_data).lmap(str)) 36 | if params.decode else 37 | NvimIOPure(value) 38 | ) 39 | 40 | 41 | @do(NvimIO[A]) 42 | def nvim_request(name: str, *args: Any, params: NRParams=NRParams.cons()) -> Do: 43 | result = yield nvim_nonfatal_request(name, *args, params=params) 44 | yield nvimio_from_either(result) 45 | 46 | 47 | def nvim_sync_request(name: str, *args: Any, params: NRParams=NRParams.cons()) -> NvimIO[A]: 48 | return nvim_request(name, *args, params=params.set.sync(True)) 49 | 50 | 51 | @do(NvimIO[A]) 52 | def typechecked_request(name: str, tpe: Type[A], *args: Any) -> Do: 53 | raw = yield nvim_sync_request(name, *args) 54 | yield ( 55 | NvimIOPure(raw) 56 | if isinstance(raw, tpe) else 57 | NvimIOError(f'invalid result type of request {name}{args}: {raw}') 58 | ) 59 | 60 | 61 | @do(NvimIO[A]) 62 | def data_cons_request( 63 | name: str, 64 | cons: Callable[[Any], Either[str, A]], 65 | *args: Any, 66 | params: NRParams=NRParams.cons(), 67 | ) -> Do: 68 | raw = yield nvim_sync_request(name, *args, params=params) 69 | parsed = yield cons(raw) 70 | yield nvimio_from_either(parsed) 71 | 72 | 73 | def data_cons_request_strict( 74 | name: str, 75 | cons: Callable[[Any], Either[str, A]], 76 | *args: Any, 77 | params: NRParams=NRParams.cons(), 78 | ) -> NvimIO[A]: 79 | return data_cons_request(name, lambda a: NvimIOPure(cons(a)), *args, params=params) 80 | 81 | 82 | @do(NvimIO[Either[str, A]]) 83 | def data_cons_request_nonfatal( 84 | name: str, 85 | cons: Callable[[Either[str, Any]], Either[str, A]], 86 | *args: Any, 87 | params: NRParams=NRParams.cons(), 88 | ) -> Do: 89 | raw = yield nvim_nonfatal_request(name, *args, params=params.set.sync(True)) 90 | return cons(raw) 91 | 92 | 93 | __all__ = ('nvim_nonfatal_request', 'nvim_request', 'typechecked_request', 'data_cons_request', 94 | 'data_cons_request_nonfatal', 'data_cons_request',) 95 | -------------------------------------------------------------------------------- /ribosome/nvim/syntax/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /ribosome/nvim/syntax/cmd.py: -------------------------------------------------------------------------------- 1 | from amino.case import Case 2 | from amino import Map 3 | from amino.logging import module_log 4 | 5 | from ribosome.nvim.syntax.expr import (SyntaxItem, SyntaxMatch, Highlight, HiLink, SyntaxKeyword, SyntaxRegion, 6 | SyntaxLiteral) 7 | 8 | log = module_log() 9 | 10 | 11 | def join_equals(data: Map[str, str]) -> str: 12 | return data.map2(lambda a, b: f'{a}={b}').join_tokens 13 | 14 | 15 | class syntax_item_cmd(Case[SyntaxItem, str], alg=SyntaxItem): 16 | 17 | def keyword(self, data: SyntaxKeyword) -> str: 18 | options = data.options.join_tokens 19 | keywords = data.keywords.join_tokens 20 | params = join_equals(data.params) 21 | return f'syntax keyword {data.group} {data.keyword} {keywords} {options} {params}' 22 | 23 | def syntax_match(self, data: SyntaxMatch) -> str: 24 | options = data.options.join_tokens 25 | params = join_equals(data.params) 26 | return f'syntax match {data.group} /{data.pattern}/ {options} {params}' 27 | 28 | def region(self, data: SyntaxRegion) -> str: 29 | skip = data.skip.map(lambda a: f' skip=/{a}/').get_or_strict('') 30 | options = data.options.join_tokens 31 | params = join_equals(data.params) 32 | return f'syntax region {data.group} start=/{data.start}/{skip} end=/{data.end}/ {options} {params}' 33 | 34 | def literal(self, data: SyntaxLiteral) -> str: 35 | return data.cmd 36 | 37 | 38 | def highlight_cmd(data: Highlight) -> str: 39 | values = join_equals(data.values) 40 | return f'highlight {data.group} {values}' 41 | 42 | 43 | def hi_link_cmd(data: HiLink) -> str: 44 | return f'highlight link {data.group} {data.target}' 45 | 46 | 47 | __all__ = ('syntax_item_cmd', 'highlight_cmd', 'hi_link_cmd',) 48 | -------------------------------------------------------------------------------- /ribosome/nvim/syntax/syntax.py: -------------------------------------------------------------------------------- 1 | from amino import Dat, List, Nil 2 | 3 | from ribosome.nvim.syntax.expr import SyntaxItem, Highlight, HiLink 4 | 5 | 6 | class Syntax(Dat['Syntax']): 7 | 8 | @staticmethod 9 | def cons( 10 | syntax: List[SyntaxItem]=Nil, 11 | highlight: List[Highlight]=Nil, 12 | links: List[HiLink]=Nil, 13 | ) -> 'Syntax': 14 | return Syntax( 15 | syntax, 16 | highlight, 17 | links, 18 | ) 19 | 20 | def __init__(self, syntax: List[SyntaxItem], highlight: List[Highlight], links: List[HiLink]) -> None: 21 | self.syntax = syntax 22 | self.highlight = highlight 23 | self.links = links 24 | 25 | 26 | __all__ = ('Syntax',) 27 | -------------------------------------------------------------------------------- /ribosome/options.py: -------------------------------------------------------------------------------- 1 | from amino.options import EnvOption 2 | 3 | development = EnvOption('RIBOSOME_DEVELOPMENT') 4 | spec = EnvOption('RIBOSOME_SPEC') 5 | file_log_level = EnvOption('RIBOSOME_FILE_LOG_LEVEL') 6 | file_log_fmt = EnvOption('RIBOSOME_FILE_LOG_FMT') 7 | nvim_log_file = EnvOption('NVIM_PYTHON_LOG_FILE') 8 | ribo_log_file = EnvOption('RIBOSOME_LOG_FILE') 9 | 10 | __all__ = ('development', 'spec', 'file_log_level', 'file_log_fmt', 'nvim_log_file', 'ribo_log_file') 11 | -------------------------------------------------------------------------------- /ribosome/rpc/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /ribosome/rpc/api.py: -------------------------------------------------------------------------------- 1 | from typing import Any, TypeVar, Callable, Generic 2 | 3 | from amino import List, Dat, Maybe, Map 4 | 5 | from ribosome.nvim.io.compute import NvimIO 6 | from ribosome.compute.program import Program 7 | from ribosome.rpc.data.prefix_style import PrefixStyle, Short, Plain 8 | from ribosome.rpc.data.rpc_method import RpcMethod, CommandMethod, FunctionMethod, AutocmdMethod 9 | from ribosome.util.doc.data import DocBlock 10 | 11 | A = TypeVar('A') 12 | default_methods = List(FunctionMethod(), CommandMethod.cons()) 13 | 14 | 15 | class RpcOptions(Dat['RpcOptions']): 16 | 17 | @staticmethod 18 | def cons( 19 | name: str=None, 20 | methods: List[RpcMethod]=default_methods, 21 | prefix: PrefixStyle=Short(), 22 | json: bool=False, 23 | write: bool=True, 24 | help: DocBlock=None, 25 | params_help: List[str]=None, 26 | json_help: Map[str, str]=None, 27 | ) -> 'RpcOptions': 28 | return RpcOptions( 29 | Maybe.optional(name), 30 | methods, 31 | prefix, 32 | json, 33 | write, 34 | help or DocBlock.empty(), 35 | Maybe.optional(params_help), 36 | Maybe.optional(json_help), 37 | ) 38 | 39 | 40 | def __init__( 41 | self, 42 | name: Maybe[str], 43 | methods: List[RpcMethod], 44 | prefix: PrefixStyle, 45 | json: bool, 46 | write: bool, 47 | help: DocBlock, 48 | params_help: Maybe[List[str]], 49 | json_help: Maybe[Map[str, str]], 50 | ) -> None: 51 | self.name = name 52 | self.methods = methods 53 | self.prefix = prefix 54 | self.json = json 55 | self.write = write 56 | self.help = help 57 | self.params_help = params_help 58 | self.json_help = json_help 59 | 60 | 61 | class RpcProgram(Generic[A], Dat['RpcProgram[A]']): 62 | 63 | @staticmethod 64 | def cons(program: Program[A], options: RpcOptions=None) -> 'RpcProgram': 65 | return RpcProgram(program, options or RpcOptions.cons()) 66 | 67 | def __init__(self, program: Program[A], options: RpcOptions) -> None: 68 | self.program = program 69 | self.options = options 70 | 71 | def conf(self, **kw: Any) -> 'RpcProgram': 72 | return self.set.options(self.options.copy(**kw)) 73 | 74 | @property 75 | def program_name(self) -> str: 76 | return self.program.name 77 | 78 | @property 79 | def rpc_name(self) -> str: 80 | return self.options.name | self.program_name 81 | 82 | 83 | class RpcApi: 84 | 85 | def simple(self, f: Callable[..., NvimIO[A]]) -> RpcProgram[A]: 86 | ... 87 | 88 | def write(self, program: Program[A]) -> RpcProgram[A]: 89 | return RpcProgram.cons(program) 90 | 91 | def read(self, program: Program[A]) -> RpcProgram[A]: 92 | return RpcProgram.cons(program, RpcOptions.cons(write=False)) 93 | 94 | def autocmd(self, program: Program[A], pattern: str=None, sync: bool=False) -> RpcProgram[A]: 95 | method = AutocmdMethod.cons(pattern, sync) 96 | return RpcProgram.cons(program, RpcOptions.cons(methods=List(method))).conf(prefix=Plain()) 97 | 98 | 99 | rpc = RpcApi() 100 | 101 | 102 | __all__ = ('RpcOptions', 'RpcProgram', 'rpc',) 103 | -------------------------------------------------------------------------------- /ribosome/rpc/arg_parser.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Any, Tuple 3 | 4 | from amino import List, Either, Right, Map 5 | from amino.json.decoder import decode_json_type 6 | from amino.logging import module_log 7 | 8 | from ribosome.rpc.args import ParamsSpec 9 | 10 | log = module_log() 11 | 12 | 13 | def starts_with_brace(data: Any) -> bool: 14 | return isinstance(data, str) and data.startswith('{') 15 | 16 | 17 | class ArgParser(abc.ABC): 18 | 19 | def __init__(self, params_spec: ParamsSpec) -> None: 20 | self.params_spec = params_spec 21 | 22 | @abc.abstractmethod 23 | def parse(self, args: List[Any]) -> Either[str, List[Any]]: 24 | ... 25 | 26 | 27 | class TokenArgParser(ArgParser): 28 | 29 | def parse(self, args: List[Any]) -> Either[str, List[Any]]: 30 | return Right(args) 31 | 32 | 33 | class JsonArgParser(ArgParser): 34 | 35 | def parse(self, args: List[Any]) -> Either[str, List[Any]]: 36 | def pick_json(start: int) -> Tuple[str, str]: 37 | return args[:start], args[start:].join_tokens 38 | strict, json_args = args.index_where(starts_with_brace).cata(pick_json, (args, '{}')) 39 | tpe = self.params_spec.types.last | (lambda: Map) 40 | return decode_json_type(json_args, tpe) / strict.cat 41 | 42 | 43 | __all__ = ('ArgParser', 'TokenArgParser', 'JsonArgParser') 44 | -------------------------------------------------------------------------------- /ribosome/rpc/concurrency.py: -------------------------------------------------------------------------------- 1 | from concurrent.futures import Future 2 | from typing import Any, Callable, TypeVar 3 | from threading import Lock 4 | 5 | from amino import do, Do, IO, Map, Dat 6 | from amino.logging import module_log 7 | 8 | from ribosome.rpc.error import RpcReadError 9 | from ribosome.rpc.data.rpc import ActiveRpc 10 | 11 | A = TypeVar('A') 12 | log = module_log() 13 | 14 | 15 | PendingRpc = Map[int, Future] 16 | 17 | 18 | class Requests(Dat['Requests']): 19 | 20 | @staticmethod 21 | def cons(current_id: int=0, to_vim: PendingRpc=Map(), from_vim: PendingRpc=Map()) -> 'Requests': 22 | return Requests(current_id, to_vim, from_vim) 23 | 24 | def __init__(self, current_id: int, to_vim: PendingRpc, from_vim: PendingRpc) -> None: 25 | self.current_id = current_id 26 | self.to_vim = to_vim 27 | self.from_vim = from_vim 28 | 29 | 30 | OnMessage = Callable[[bytes], IO[None]] 31 | OnError = Callable[[RpcReadError], IO[None]] 32 | 33 | 34 | class RpcConcurrency(Dat['RpcConcurrency']): 35 | 36 | @staticmethod 37 | def cons( 38 | requests: Requests=None, 39 | lock: Lock=None, 40 | ) -> 'RpcConcurrency': 41 | return RpcConcurrency( 42 | requests or Requests.cons(), 43 | lock or Lock(), 44 | ) 45 | 46 | def exclusive(self, f: Callable[..., IO[A]], *a: Any, **kw: Any) -> IO[A]: 47 | def wrap() -> IO[A]: 48 | with self.lock: 49 | return IO.from_either(f(*a, **kw).attempt) 50 | return IO.suspend(wrap) 51 | 52 | def __init__(self, requests: Requests, lock: Lock) -> None: 53 | self.requests = requests 54 | self.lock = lock 55 | 56 | 57 | def exclusive_unregister_rpc(rc: RpcConcurrency, requests: PendingRpc, rpc: ActiveRpc) -> IO[Future]: 58 | return IO.delay(requests.pop, rpc.id) 59 | 60 | 61 | def unregister_rpc(rc: RpcConcurrency, requests: PendingRpc, rpc: ActiveRpc) -> IO[Future]: 62 | log.debug1(f'unregistering {rpc}') 63 | return ( 64 | IO.failed(f'invalid request id from vim after execution: {rpc}. active requests: {requests}') 65 | if rpc.id not in requests else 66 | rc.exclusive(exclusive_unregister_rpc, rc, requests, rpc) 67 | ) 68 | 69 | 70 | @do(IO[Future]) 71 | def exclusive_register_rpc(rc: RpcConcurrency, requests: PendingRpc, rpc: ActiveRpc) -> Do: 72 | f: Future = Future() 73 | yield IO.delay(requests.update, {rpc.id: f}) 74 | return f 75 | 76 | 77 | @do(IO[Future]) 78 | def register_rpc(rc: RpcConcurrency, requests: PendingRpc, rpc: ActiveRpc) -> Do: 79 | log.debug1(f'registering {rpc}') 80 | yield ( 81 | IO.failed(f'duplicate id in request from vim: {rpc}') 82 | if rpc.id in requests else 83 | rc.exclusive(exclusive_register_rpc, rc, requests, rpc) 84 | ) 85 | 86 | __all__ = ('Requests', 'OnMessage', 'OnError', 'RpcConcurrency', 'unregister_rpc', 'register_rpc',) 87 | -------------------------------------------------------------------------------- /ribosome/rpc/data/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /ribosome/rpc/data/nargs.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | from amino import List, Maybe, Just, Nothing 4 | from amino.dat import ADT 5 | 6 | 7 | class Nargs(ADT['Nargs']): 8 | 9 | @staticmethod 10 | def cons(min: int, mmax: Maybe[int]) -> 'Nargs': 11 | def from_min() -> Nargs: 12 | return NargsStar() if min == 0 else NargsPlus() 13 | def with_max(max: int) -> Nargs: 14 | return ( 15 | Just(NargsZero()) 16 | if max == 0 else 17 | ( 18 | Just(NargsOne()) 19 | if min == 1 else 20 | Just(NargsQM()) 21 | ) 22 | if max == 1 else 23 | Nothing 24 | ) 25 | return mmax.flat_map(with_max) | from_min 26 | 27 | @abc.abstractproperty 28 | def for_vim(self) -> str: 29 | ... 30 | 31 | def _arg_desc(self) -> List[str]: 32 | return List() 33 | 34 | 35 | class NargsZero(Nargs): 36 | 37 | @property 38 | def for_vim(self) -> str: 39 | return '0' 40 | 41 | 42 | class NargsOne(Nargs): 43 | 44 | @property 45 | def for_vim(self) -> str: 46 | return '1' 47 | 48 | 49 | class NargsStar(Nargs): 50 | 51 | @property 52 | def for_vim(self) -> str: 53 | return '*' 54 | 55 | 56 | class NargsPlus(Nargs): 57 | 58 | @property 59 | def for_vim(self) -> str: 60 | return '+' 61 | 62 | 63 | class NargsQM(Nargs): 64 | 65 | @property 66 | def for_vim(self) -> str: 67 | return '?' 68 | 69 | __all__ = ('Nargs',) 70 | -------------------------------------------------------------------------------- /ribosome/rpc/data/prefix_style.py: -------------------------------------------------------------------------------- 1 | from amino import ADT, Boolean 2 | 3 | 4 | class PrefixStyle(ADT['PrefixStyle']): 5 | 6 | @property 7 | def short(self) -> Boolean: 8 | return Boolean.isinstance(self, Short) 9 | 10 | @property 11 | def full(self) -> Boolean: 12 | return Boolean.isinstance(self, Full) 13 | 14 | @property 15 | def plain(self) -> Boolean: 16 | return Boolean.isinstance(self, Plain) 17 | 18 | 19 | class Short(PrefixStyle): pass 20 | 21 | 22 | class Full(PrefixStyle): pass 23 | 24 | 25 | class Plain(PrefixStyle): pass 26 | 27 | 28 | 29 | __all__ = ('PrefixStyle', 'Short', 'Full', 'Plain',) 30 | -------------------------------------------------------------------------------- /ribosome/rpc/data/rpc.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from amino import Dat, List, Nil 4 | 5 | from ribosome.rpc.data.rpc_type import NonblockingRpc, RpcType, BlockingRpc 6 | 7 | 8 | class Rpc(Dat['Rpc']): 9 | 10 | @staticmethod 11 | def nonblocking(method: str, args: List[Any]) -> 'Rpc': 12 | return Rpc(method, args, NonblockingRpc()) 13 | 14 | def __init__(self, method: str, args: List[Any], tpe: RpcType) -> None: 15 | self.method = method 16 | self.args = args 17 | self.tpe = tpe 18 | 19 | @property 20 | def sync(self) -> bool: 21 | return isinstance(self.tpe, BlockingRpc) 22 | 23 | 24 | class ActiveRpc(Dat['ActiveRpc']): 25 | 26 | def __init__(self, rpc: Rpc, id: int) -> None: 27 | self.rpc = rpc 28 | self.id = id 29 | 30 | 31 | class RpcArgs(Dat['RpcArgs']): 32 | 33 | @staticmethod 34 | def cons(args: List[Any]=Nil, bang: bool=False) -> 'RpcArgs': 35 | return RpcArgs(args, bang) 36 | 37 | def __init__(self, args: List[Any], bang: bool) -> None: 38 | self.args = args 39 | self.bang = bang 40 | 41 | @property 42 | def string(self) -> str: 43 | return self.args.join_comma 44 | 45 | 46 | __all__ = ('Rpc', 'ActiveRpc', 'RpcArgs',) 47 | -------------------------------------------------------------------------------- /ribosome/rpc/data/rpc_method.py: -------------------------------------------------------------------------------- 1 | from amino import ADT 2 | 3 | 4 | class RpcMethod(ADT['RpcMethod']): 5 | pass 6 | 7 | 8 | class CommandMethod(RpcMethod): 9 | 10 | @staticmethod 11 | def cons(bang: bool=False) -> 'CommandMethod': 12 | return CommandMethod(bang) 13 | 14 | def __init__(self, bang: bool) -> None: 15 | self.bang = bang 16 | 17 | 18 | class FunctionMethod(RpcMethod): 19 | 20 | @property 21 | def method(self) -> str: 22 | return 'function' 23 | 24 | 25 | class AutocmdMethod(RpcMethod): 26 | 27 | @staticmethod 28 | def cons(pattern: str=None, sync: bool=False) -> 'AutocmdMethod': 29 | return AutocmdMethod(pattern or '*', sync) 30 | 31 | def __init__(self, pattern: str, sync: bool) -> None: 32 | self.pattern = pattern 33 | self.sync = sync 34 | 35 | 36 | __all__ = ('RpcMethod', 'CommandMethod', 'FunctionMethod', 'AutocmdMethod',) 37 | -------------------------------------------------------------------------------- /ribosome/rpc/data/rpc_type.py: -------------------------------------------------------------------------------- 1 | from amino import ADT 2 | 3 | 4 | class RpcType(ADT['RpcType']): 5 | pass 6 | 7 | 8 | class BlockingRpc(RpcType): 9 | 10 | def __init__(self, id: int) -> None: 11 | self.id = id 12 | 13 | 14 | class NonblockingRpc(RpcType): 15 | pass 16 | 17 | 18 | __all__ = ('RpcType', 'BlockingRpc', 'NonblockingRpc',) 19 | -------------------------------------------------------------------------------- /ribosome/rpc/error.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional 2 | 3 | from amino import ADT, do, Do, List 4 | from amino.logging import module_log 5 | 6 | from ribosome.nvim.io.compute import NvimIO 7 | from ribosome.nvim.api.exists import function_exists 8 | from ribosome.nvim.api.function import define_function 9 | 10 | log = module_log() 11 | 12 | 13 | class RpcReadError(ADT['RpcReadError']): 14 | pass 15 | 16 | 17 | class RpcProcessExit(RpcReadError): 18 | pass 19 | 20 | 21 | class RpcReadErrorUnknown(RpcReadError): 22 | 23 | def __init__(self, message: str) -> None: 24 | self.message = message 25 | 26 | 27 | def processing_error(data: Optional[bytes]) -> Callable[[Exception], None]: 28 | def processing_error(error: Exception) -> None: 29 | if data: 30 | log.debug(f'{error}: {data}') 31 | log.error(f'error processing message from nvim: {error}') 32 | return processing_error 33 | 34 | 35 | stderr_handler_body = ''' 36 | let err = substitute(join(a:data, '\\r'), '"', '\\"', 'g') 37 | try 38 | python3 import amino 39 | python3 from ribosome.logging import ribosome_envvar_file_logging 40 | python3 ribosome_envvar_file_logging() 41 | execute 'python3 amino.amino_log.error(f"""error starting rpc job on channel ' . a:id . ':\\r' . err . '""")' 42 | catch // 43 | echohl ErrorMsg 44 | echo err 45 | echohl None 46 | endtry 47 | ''' 48 | 49 | 50 | def rpc_stderr_handler_name(prefix: str) -> str: 51 | return f'{prefix}RpcStderr' 52 | 53 | 54 | @do(NvimIO[str]) 55 | def define_rpc_stderr_handler(prefix: str) -> Do: 56 | name = rpc_stderr_handler_name(prefix) 57 | exists = yield function_exists(name) 58 | if not exists: 59 | yield define_function(name, List('id', 'data', 'event'), stderr_handler_body) 60 | return name 61 | 62 | 63 | __all__ = ('RpcReadError', 'RpcProcessExit', 'RpcReadErrorUnknown', 'processing_error', 'define_rpc_stderr_handler', 64 | 'rpc_stderr_handler_name',) 65 | -------------------------------------------------------------------------------- /ribosome/rpc/from_vim.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from amino import do, Do, IO, List 4 | 5 | from amino.logging import module_log 6 | 7 | from ribosome.rpc.comm import Comm, Exec 8 | from ribosome.rpc.to_vim import handle_response, RpcResponse 9 | from ribosome.rpc.nvim_api import RiboNvimApi 10 | from ribosome.rpc.data.rpc import Rpc 11 | from ribosome.nvim.io.data import NFatal, NResult 12 | from ribosome.rpc.response import validate_rpc_result, report_error 13 | 14 | log = module_log() 15 | 16 | 17 | # TODO fetch nvim api ctor from `Comm` 18 | @do(IO[NResult[List[Any]]]) 19 | def execute_rpc(comm: Comm, rpc: Rpc, execute: Exec, plugin_name: str) -> Do: 20 | thunk = yield IO.delay(execute, rpc.method, rpc.args) 21 | yield IO.delay(thunk.run_a, RiboNvimApi(plugin_name, comm)) 22 | 23 | 24 | @do(IO[RpcResponse]) 25 | def execute_rpc_safe(comm: Comm, rpc: Rpc, execute: Exec, plugin_name: str) -> Do: 26 | result = yield execute_rpc(comm, rpc, execute, plugin_name).recover(NFatal) 27 | validated = validate_rpc_result(rpc)(result) 28 | yield report_error.match(validated) 29 | return validated 30 | 31 | 32 | @do(IO[Any]) 33 | def execute_rpc_from_vim(rpc: Rpc, comm: Comm, execute: Exec, plugin_name: str) -> Do: 34 | result = yield execute_rpc_safe(comm, rpc, execute, plugin_name) 35 | yield handle_response.match(result).run(comm) 36 | 37 | 38 | __all__ = ('execute_rpc_from_vim') 39 | -------------------------------------------------------------------------------- /ribosome/rpc/handle_receive.py: -------------------------------------------------------------------------------- 1 | from concurrent.futures import Future 2 | from typing import Callable, Tuple, Generic, TypeVar, Any 3 | 4 | import msgpack 5 | 6 | from amino import do, Do, IO, Right, Left, List, Lists 7 | from amino.case import Case 8 | from amino.logging import module_log 9 | 10 | from ribosome.rpc.receive import (cons_receive, ReceiveResponse, ReceiveError, Receive, ReceiveRequest, 11 | ReceiveNotification, ReceiveExit, ReceiveUnknown) 12 | from ribosome.rpc.comm import Comm 13 | from ribosome.rpc.concurrency import Requests, RpcConcurrency 14 | from ribosome.rpc.data.rpc import Rpc 15 | from ribosome.rpc.data.rpc_type import BlockingRpc 16 | 17 | log = module_log() 18 | A = TypeVar('A') 19 | 20 | 21 | def resolve_rpc(requests: Requests, id: int) -> IO[Tuple[Future, Rpc]]: 22 | return IO.delay(requests.to_vim.pop, id) 23 | 24 | 25 | @do(IO[None]) 26 | def publish_response_from_vim(requests: Requests, data: ReceiveResponse) -> Do: 27 | fut = yield resolve_rpc(requests, data.id) 28 | yield IO.delay(fut.set_result, Right(data.data)) 29 | 30 | 31 | @do(IO[None]) 32 | def publish_error_from_vim(requests: Requests, err: ReceiveError) -> Do: 33 | fut = yield resolve_rpc(requests, err.id) 34 | yield IO.delay(fut.set_result, Left(err.error)) 35 | 36 | 37 | class handle_receive(Generic[A], Case[Receive, IO[None]], alg=Receive): 38 | 39 | def __init__(self, comm: Comm, execute_plugin_rpc: Callable[[Comm, Rpc], IO[None]]) -> None: 40 | self.comm = comm 41 | self.execute_plugin_rpc = execute_plugin_rpc 42 | 43 | @property 44 | def concurrency(self) -> RpcConcurrency: 45 | return self.comm.concurrency 46 | 47 | @property 48 | def requests(self) -> Requests: 49 | return self.concurrency.requests 50 | 51 | def response(self, data: ReceiveResponse) -> IO[None]: 52 | return self.concurrency.exclusive(publish_response_from_vim, self.requests, data) 53 | 54 | def error(self, err: ReceiveError) -> IO[None]: 55 | return self.concurrency.exclusive(publish_error_from_vim, self.requests, err) 56 | 57 | def request(self, receive: ReceiveRequest) -> IO[None]: 58 | return self.execute_plugin_rpc(self.comm, Rpc(receive.method, receive.args, BlockingRpc(receive.id))) 59 | 60 | def notification(self, receive: ReceiveNotification) -> IO[None]: 61 | return self.execute_plugin_rpc(self.comm, Rpc.nonblocking(receive.method, receive.args)) 62 | 63 | def exit(self, receive: ReceiveExit) -> IO[None]: 64 | log.debug('exiting rpc session') 65 | return IO.pure(None) 66 | 67 | def unknown(self, receive: ReceiveUnknown) -> IO[None]: 68 | return IO.delay(log.error, f'received unknown rpc: {receive}') 69 | 70 | 71 | @do(IO[List[Any]]) 72 | def unpack(data: bytes) -> Do: 73 | unpacker = msgpack.Unpacker() 74 | yield IO.delay(unpacker.feed, data) 75 | raw = yield IO.delay(list, unpacker) 76 | return Lists.wrap(raw) 77 | 78 | 79 | def rpc_receive(comm: Comm, execute_plugin_rpc: Callable[[Comm, Rpc], None]) -> Callable[[bytes], IO[None]]: 80 | def handle(data: Any) -> IO[None]: 81 | receive = cons_receive(data) 82 | return handle_receive(comm, execute_plugin_rpc)(receive) 83 | @do(IO[None]) 84 | def on_read(blob: bytes) -> Do: 85 | data = yield IO.delay(unpack, blob).recover_with(lambda e: IO.failed(f'failed to unpack: {e}')) 86 | yield data.traverse(handle, IO) 87 | yield IO.pure(None) 88 | return on_read 89 | 90 | 91 | __all__ = ('rpc_receive',) 92 | -------------------------------------------------------------------------------- /ribosome/rpc/io/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /ribosome/rpc/io/connect.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from asyncio import get_child_watcher, run_coroutine_threadsafe 3 | from typing import Callable, Coroutine 4 | 5 | from amino import IO, do, Do, Try 6 | from amino.case import Case 7 | from amino.logging import module_log 8 | 9 | from ribosome.rpc.concurrency import OnMessage, OnError 10 | from ribosome.rpc.io.data import (AsyncioResources, AsyncioPipes, Asyncio, AsyncioEmbed, EmbedProto, AsyncioStdio, 11 | BasicProto, AsyncioSocket) 12 | 13 | log = module_log() 14 | 15 | 16 | class connect_asyncio(Case[AsyncioPipes, IO[None]], alg=AsyncioPipes): 17 | 18 | def __init__(self, asio: Asyncio, on_message: OnMessage, on_error: OnError) -> None: 19 | self.asio = asio 20 | self.on_message = on_message 21 | self.on_error = on_error 22 | 23 | @do(IO[Coroutine]) 24 | def embed(self, a: AsyncioEmbed) -> Do: 25 | child_watcher = yield IO.delay(get_child_watcher) 26 | yield IO.delay(child_watcher.attach_loop, self.asio.loop) 27 | return self.asio.loop.subprocess_exec(lambda: EmbedProto(self.asio, self.on_message, self.on_error), *a.proc) 28 | 29 | def stdio(self, pipes: AsyncioStdio) -> IO[Coroutine]: 30 | proto = BasicProto(self.asio, self.on_message, self.on_error) 31 | async def connect() -> None: 32 | await self.asio.loop.connect_read_pipe(lambda: proto, sys.stdin) 33 | await self.asio.loop.connect_write_pipe(lambda: proto, sys.stdout) 34 | return IO.pure(connect()) 35 | 36 | def socket(self, pipes: AsyncioSocket) -> IO[Coroutine]: 37 | return IO.pure(self.asio.loop.create_unix_connection( 38 | lambda: BasicProto(self.asio, self.on_message, self.on_error), 39 | str(pipes.path), 40 | )) 41 | 42 | 43 | def asyncio_main_loop(asio: Asyncio) -> None: 44 | try: 45 | asio.loop.run_forever() 46 | except Exception as e: 47 | log.error(e) 48 | 49 | 50 | @do(IO[None]) 51 | def stop_asyncio_loop(asio: Asyncio) -> Do: 52 | yield IO.delay(asio.loop.stop) 53 | yield asio.thread.thread.cata(lambda t: IO.delay(t.join, 3), IO.pure(None)) 54 | yield IO.delay(asio.loop.close) 55 | yield IO.delay(asio.thread.reset) 56 | 57 | 58 | def start_processing(asio: Asyncio) -> Callable[[OnMessage, OnError], IO[None]]: 59 | @do(IO[None]) 60 | def start(on_message: OnMessage, on_error: OnError) -> Do: 61 | thread = yield IO.fork(asyncio_main_loop, asio) 62 | yield IO.delay(asio.thread.update, thread) 63 | connect = yield connect_asyncio(asio, on_message, on_error)(asio.pipes) 64 | conn_finished = yield IO.delay(run_coroutine_threadsafe, connect, asio.loop) 65 | yield IO.delay(conn_finished.result) 66 | return start 67 | 68 | 69 | def stop_processing(asio: Asyncio) -> Callable[[], IO[None]]: 70 | def stop() -> IO[None]: 71 | return stop_asyncio_loop(asio) 72 | return stop 73 | 74 | 75 | # FIXME stop loop on error? 76 | def asyncio_send(resources: AsyncioResources) -> Callable[[bytes], None]: 77 | def send(data: bytes) -> None: 78 | transport = resources.transport.result() 79 | Try(transport.write, data).lmap(lambda err: log.error(f'asyncio write failed: {err}')) 80 | return send 81 | 82 | 83 | def join_asyncio_loop(asio: Asyncio) -> IO[None]: 84 | return asio.thread.thread.cata(lambda t: IO.delay(t.join), IO.failed(f'no asyncio loop running')) 85 | 86 | 87 | def asyncio_exit(asio: Asyncio) -> None: 88 | pass 89 | 90 | 91 | __all__ = ('asyncio_exit', 'join_asyncio_loop', 'asyncio_send', 'stop_processing', 'start_processing',) 92 | -------------------------------------------------------------------------------- /ribosome/rpc/io/start.py: -------------------------------------------------------------------------------- 1 | from asyncio import new_event_loop 2 | from typing import Tuple 3 | from concurrent.futures import Future 4 | 5 | from amino import List, IO, do, Do, Path 6 | from amino.logging import module_log 7 | 8 | from ribosome.rpc.comm import RpcComm 9 | from ribosome.config.config import Config 10 | from ribosome.rpc.start import start_plugin_sync, cannot_execute_request, init_comm 11 | from ribosome.rpc.nvim_api import RiboNvimApi 12 | from ribosome.rpc.io.data import AsyncioPipes, Asyncio, AsyncioResources, AsyncioEmbed, AsyncioStdio, AsyncioSocket 13 | from ribosome.rpc.io.connect import start_processing, stop_processing, asyncio_send, join_asyncio_loop, asyncio_exit 14 | 15 | log = module_log() 16 | embed_nvim_cmdline = List('nvim', '-n', '-u', 'NONE', '--embed') 17 | 18 | 19 | def cons_asyncio(pipes: AsyncioPipes) -> Tuple[Asyncio, RpcComm]: 20 | loop = new_event_loop() 21 | resources = AsyncioResources.cons(Future()) 22 | asio = Asyncio.cons(loop, pipes, resources) 23 | comm = RpcComm( 24 | start_processing(asio), 25 | stop_processing(asio), 26 | asyncio_send(resources), 27 | lambda: join_asyncio_loop(asio), 28 | lambda: asyncio_exit(asio), 29 | ) 30 | return asio, comm 31 | 32 | 33 | def cons_asyncio_embed(proc: List[str]) -> Tuple[Asyncio, RpcComm]: 34 | return cons_asyncio(AsyncioEmbed(proc)) 35 | 36 | 37 | def cons_asyncio_stdio() -> Tuple[Asyncio, RpcComm]: 38 | return cons_asyncio(AsyncioStdio()) 39 | 40 | 41 | def cons_asyncio_socket(path: Path) -> Tuple[Asyncio, RpcComm]: 42 | return cons_asyncio(AsyncioSocket(path)) 43 | 44 | 45 | def start_asyncio_plugin_sync(config: Config) -> IO[None]: 46 | asio, rpc_comm = cons_asyncio_stdio() 47 | return start_plugin_sync(config, rpc_comm) 48 | 49 | 50 | @do(IO[RiboNvimApi]) 51 | def start_asyncio_embed_nvim_sync(name: str, extra: List[str]) -> Do: 52 | asio, rpc_comm = cons_asyncio_embed(embed_nvim_cmdline + extra) 53 | comm = yield init_comm(rpc_comm, cannot_execute_request) 54 | return RiboNvimApi(name, comm) 55 | 56 | 57 | def start_asyncio_embed_nvim_sync_log(name: str, log: Path) -> IO[RiboNvimApi]: 58 | return start_asyncio_embed_nvim_sync(name, List(f'-V{log}')) 59 | 60 | 61 | __all__ = ('cons_asyncio_embed', 'cons_asyncio_stdio', 'cons_asyncio_socket', 'start_asyncio_plugin_sync', 62 | 'start_asyncio_embed_nvim_sync', 'start_asyncio_embed_nvim_sync_log',) 63 | -------------------------------------------------------------------------------- /ribosome/rpc/nvim_api.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Tuple 2 | 3 | from amino import List, do, Either, Do, Left 4 | from amino.logging import module_log 5 | 6 | from ribosome import NvimApi 7 | from ribosome.rpc.comm import Comm 8 | from ribosome.rpc.to_vim import send_request, send_notification 9 | from ribosome.rpc.data.rpc import Rpc 10 | 11 | log = module_log() 12 | 13 | 14 | class RiboNvimApi(NvimApi): 15 | 16 | def __init__(self, name: str, comm: Comm) -> None: 17 | self.name = name 18 | self.comm = comm 19 | 20 | @do(Either[str, Tuple[NvimApi, Any]]) 21 | def request(self, method: str, args: List[Any], sync: bool, timeout: float) -> Do: 22 | sender = send_request if sync else send_notification 23 | rpc_desc = 'request' if sync else 'notification' 24 | try: 25 | log.debug1(lambda: f'api: {rpc_desc} `{method}({args.join_tokens})`') 26 | a = sender(Rpc.nonblocking(method, args), timeout) 27 | comm, result = yield a.run(self.comm).attempt 28 | return self.copy(comm=comm), result 29 | except Exception as e: 30 | yield Left(f'request error: {e}') 31 | 32 | 33 | __all__ = ('RiboNvimApi',) 34 | -------------------------------------------------------------------------------- /ribosome/rpc/state.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Any 2 | 3 | from amino import Nil 4 | from amino.do import do, Do 5 | 6 | from ribosome.config.config import Config 7 | from ribosome.logging import nvim_logging 8 | from ribosome.data.plugin_state import PluginState 9 | from ribosome.nvim.io.compute import NvimIO 10 | from ribosome.nvim.io.api import N 11 | from ribosome.config.component import ComponentConfig 12 | from ribosome.components.internal.update import init_rpc_plugin 13 | from ribosome.compute.interpret import ProgIOInterpreter 14 | from ribosome.compute.program import Program 15 | 16 | D = TypeVar('D') 17 | CC = TypeVar('CC') 18 | 19 | 20 | def cons_state( 21 | config: Config, 22 | io_interpreter: ProgIOInterpreter=None, 23 | logger: Program[None]=None, 24 | **kw: Any, 25 | ) -> PluginState[D, CC]: 26 | data = config.basic.state_ctor() 27 | return PluginState.cons( 28 | config.basic, 29 | ComponentConfig(config.components), 30 | config.rpc, 31 | data, 32 | Nil, 33 | config.init, 34 | logger=logger, 35 | io_interpreter=io_interpreter, 36 | **kw, 37 | ) 38 | 39 | 40 | @do(NvimIO[PluginState[D, CC]]) 41 | def init_state_plugin(config: Config, io_interpreter: ProgIOInterpreter=None, logger: Program[None]=None) -> Do: 42 | log_handler = yield N.delay(nvim_logging) 43 | state = cons_state(config, io_interpreter, logger, log_handler=log_handler) 44 | yield init_rpc_plugin().run_s(state) 45 | 46 | 47 | __all__ = ('cons_state', 'init_state',) 48 | -------------------------------------------------------------------------------- /ribosome/rpc/strict.py: -------------------------------------------------------------------------------- 1 | from queue import Queue 2 | from typing import Any 3 | 4 | import msgpack 5 | 6 | from amino import Dat, Map, IO 7 | 8 | from ribosome.rpc.comm import OnError 9 | from ribosome.rpc.concurrency import OnMessage 10 | from ribosome.rpc.error import processing_error 11 | 12 | 13 | class StrictRpc(Dat['StrictRpc']): 14 | 15 | @staticmethod 16 | def cons(responses: Map[str, Any]) -> 'StrictRpc': 17 | return StrictRpc(responses, Queue(), False) 18 | 19 | def __init__(self, responses: Map[str, Any], queue: Queue, running: bool) -> None: 20 | self.responses = responses 21 | self.queue = queue 22 | self.running = running 23 | 24 | def send(self, data: bytes) -> None: 25 | self.queue.put(msgpack.unpackb(data)) 26 | 27 | def start_processing(self, on_message: OnMessage, on_error: OnError) -> IO[None]: 28 | self.running = True 29 | return IO.fork(self.loop, on_message, on_error) 30 | 31 | def loop(self, on_message: OnMessage, on_error: OnError) -> None: 32 | while self.running: 33 | try: 34 | a = self.queue.get(timeout=.1) 35 | except Exception as e: 36 | pass 37 | else: 38 | if len(a) == 4 and a[0] == 0: 39 | response = self.responses.lift(a[2].decode()).get_or_else(None) 40 | on_message(msgpack.packb([1, a[1], None, response])).attempt.lmap(processing_error) 41 | 42 | def stop(self) -> None: 43 | self.running = False 44 | 45 | 46 | __all__ = ('StrictRpc',) 47 | -------------------------------------------------------------------------------- /ribosome/rpc/to_plugin.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, TypeVar 2 | 3 | from amino import List, do, Do, Nil 4 | from amino.logging import module_log 5 | from amino.util.string import decode 6 | 7 | from ribosome.nvim.io.compute import NvimIO 8 | from ribosome.rpc.comm import StateGuard, exclusive_ns 9 | from ribosome.nvim.io.api import N 10 | from ribosome.compute.program import Program 11 | from ribosome.nvim.io.state import NS 12 | from ribosome.data.plugin_state import PS 13 | from ribosome.compute.run import run_prog 14 | from ribosome.compute.api import parse_args 15 | from ribosome.rpc.api import RpcProgram 16 | from ribosome.rpc.data.rpc import RpcArgs 17 | from ribosome.nvim.api.util import nvimio_repeat_timeout 18 | 19 | log = module_log() 20 | A = TypeVar('A') 21 | 22 | 23 | @do(NS[PS, Any]) 24 | def run_program(rpc_program: RpcProgram, args: RpcArgs) -> Do: 25 | parsed_args = yield NS.from_either(parse_args(rpc_program, args.args)) 26 | yield run_prog(rpc_program.program, parsed_args) 27 | 28 | 29 | def run_programs(programs: List[Program], args: RpcArgs) -> NvimIO[List[Any]]: 30 | return programs.traverse(lambda a: run_program(a, args), NvimIO) 31 | 32 | 33 | def run_program_exclusive(guard: StateGuard[A], program: RpcProgram, args: RpcArgs) -> NvimIO[Any]: 34 | return ( 35 | exclusive_ns(guard, program.program.name, run_program, program, args) 36 | if program.options.write else 37 | run_program(program, args).run_a(guard.state) 38 | ) 39 | 40 | 41 | def run_programs_exclusive(guard: StateGuard[A], programs: List[Program], args: RpcArgs) -> NvimIO[List[Any]]: 42 | return programs.traverse(lambda a: run_program_exclusive(guard, a, args), NvimIO) 43 | 44 | 45 | def no_programs_for_rpc(method: str, args: RpcArgs) -> NvimIO[A]: 46 | return N.error(f'no programs defined for request {method}({args.string})') 47 | 48 | 49 | def decode_args(method: str, args: List[Any]) -> RpcArgs: 50 | is_event = method.endswith('_event') 51 | decoded_args = decode(args) 52 | fun_args = ( 53 | decoded_args 54 | if is_event else 55 | decoded_args.head | Nil 56 | ) 57 | bang = not is_event and decoded_args.lift(1).contains(1) 58 | return RpcArgs(fun_args, bang) 59 | 60 | 61 | def rpc_handler(guard: StateGuard[A]) -> Callable[[str, List[Any]], NvimIO[List[Any]]]: 62 | @do(NvimIO[List[Any]]) 63 | def handler(method: str, raw_args: List[Any]) -> Do: 64 | yield nvimio_repeat_timeout(lambda: N.pure(guard), lambda a: a.initialized, '''state wasn't initialized''', 20) 65 | args = decode_args(method, raw_args) 66 | log.debug(f'handling request: {method}({args.args.join_comma})') 67 | programs = guard.state.programs_by_name(method) 68 | yield ( 69 | no_programs_for_rpc(method, args) 70 | if programs.empty else 71 | run_programs_exclusive(guard, programs, args) 72 | ) 73 | return handler 74 | 75 | 76 | __all__ = ('rpc_handler', 'run_program', 'run_programs', 'run_program_exclusive', 'run_programs_exclusive', 77 | 'no_programs_for_rpc', 'decode_args', 'rpc_handler',) 78 | -------------------------------------------------------------------------------- /ribosome/rpc/uv/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /ribosome/test/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = () 2 | -------------------------------------------------------------------------------- /ribosome/test/integration/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = () 2 | -------------------------------------------------------------------------------- /ribosome/test/integration/embed.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Any 2 | 3 | from kallikrein import Expectation 4 | 5 | from amino import do, Do, IO, Lists 6 | from amino.logging import module_log 7 | from amino.string.hues import green 8 | 9 | from ribosome.nvim.io.compute import NvimIO 10 | from ribosome.nvim.io.api import N 11 | from ribosome.nvim.api.rpc import nvim_quit 12 | from ribosome.nvim.io.data import NResult 13 | from ribosome.test.config import TestConfig 14 | from ribosome.test.integration.rpc import setup_test_nvim_embed, set_nvim_vars 15 | from ribosome.test.run import run_test_io, plugin_started 16 | from ribosome.test.integration.start import start_plugin_embed 17 | 18 | log = module_log() 19 | 20 | 21 | def cleanup(config: TestConfig) -> Callable[[NResult], NvimIO[None]]: 22 | @do(NvimIO[None]) 23 | def cleanup(result: NResult) -> Do: 24 | yield nvim_quit() 25 | runtime_log = yield N.from_io(IO.delay(config.log_file.read_text)) 26 | if runtime_log: 27 | log.info(green('plugin output:')) 28 | Lists.lines(runtime_log) % log.info 29 | log.info('') 30 | return cleanup 31 | 32 | 33 | def plugin_test(config: TestConfig, io: Callable[..., NvimIO[Expectation]], *a: Any, **kw: Any) -> Expectation: 34 | @do(NvimIO[None]) 35 | def run_nvim_io() -> Do: 36 | yield set_nvim_vars(config.vars + ('components', config.components)) 37 | yield config.pre() 38 | if config.autostart: 39 | yield start_plugin_embed(config) 40 | yield plugin_started() 41 | yield io(*a, **kw) 42 | @do(IO[Expectation]) 43 | def run() -> Do: 44 | nvim = yield setup_test_nvim_embed(config) 45 | yield N.to_io_a(N.ensure(run_nvim_io(), cleanup(config)), nvim.api) 46 | return run_test_io(run) 47 | 48 | 49 | __all__ = ('plugin_test',) 50 | -------------------------------------------------------------------------------- /ribosome/test/integration/external.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Any 2 | 3 | from kallikrein import Expectation 4 | 5 | from amino import do, IO, Do, Map 6 | from amino.test import temp_dir 7 | 8 | from ribosome.nvim.io.compute import NvimIO 9 | from ribosome.test.integration.embed import setup_test_nvim_embed, TestConfig 10 | from ribosome.nvim.api.rpc import nvim_quit 11 | from ribosome.nvim.io.api import N 12 | from ribosome.nvim.io.data import NResult 13 | from ribosome.data.plugin_state import PS 14 | from ribosome.nvim.io.state import NS 15 | from ribosome.test.prog import init_test_state 16 | from ribosome.test.run import run_test_io 17 | from ribosome.test.integration.rpc import set_nvim_vars 18 | 19 | 20 | @do(NvimIO[None]) 21 | def cleanup(result: NResult) -> Do: 22 | '''for some reason, the loop gets stuck if `quit` is called only once. 23 | ''' 24 | yield nvim_quit() 25 | 26 | 27 | @do(NvimIO[Expectation]) 28 | def run_test(config: TestConfig, io: Callable[..., NS[PS, Expectation]], *a: Any, **kw: Any) -> Do: 29 | default_vars = Map(ribosome_session_name='spec', ribosome_state_dir=str(temp_dir('persist'))) 30 | yield set_nvim_vars(default_vars ** config.vars + ('components', config.components)) 31 | yield config.pre() 32 | state = yield init_test_state(config) 33 | yield io(*a, **kw).run_a(state) 34 | 35 | 36 | def external_state_test(config: TestConfig, io: Callable[..., NS[PS, Expectation]], *a: Any, **kw: Any) -> Expectation: 37 | @do(IO[Expectation]) 38 | def run() -> Do: 39 | nvim = yield setup_test_nvim_embed(config) 40 | yield N.to_io_a(N.ensure(run_test(config, io, *a, **kw), cleanup), nvim.api) 41 | return run_test_io(run) 42 | 43 | 44 | def external_test(config: TestConfig, io: Callable[..., NvimIO[Expectation]], *a: Any, **kw: Any) -> Expectation: 45 | return external_state_test(config, lambda: NS.lift(io())) 46 | 47 | 48 | __all__ = ('external_test', 'external_state_test',) 49 | -------------------------------------------------------------------------------- /ribosome/test/integration/rpc.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Tuple 2 | 3 | from amino import do, Do, IO, List, Dat, Map, Path 4 | from amino.test import temp_dir 5 | from amino.test.path import pkg_dir 6 | from amino.env_vars import set_env 7 | 8 | from ribosome import NvimApi 9 | from ribosome.rpc.start import start_external 10 | from ribosome.rpc.comm import Comm 11 | from ribosome.test.config import TestConfig 12 | from ribosome.nvim.io.compute import NvimIO 13 | from ribosome.nvim.api.variable import variable_set 14 | from ribosome.rpc.nvim_api import RiboNvimApi 15 | from ribosome.rpc.io.start import cons_asyncio_socket, cons_asyncio_embed 16 | 17 | nvim_cmdline = List('nvim', '-n', '-u', 'NONE') 18 | env_vars = List( 19 | ('RIBOSOME_SPEC', 1), 20 | ('AMINO_DEVELOPMENT', 1), 21 | ('RIBOSOME_PKG_DIR', str(pkg_dir())), 22 | ('RIBOSOME_FILE_LOG_FMT', '{levelname} {name}:{message}') 23 | ) 24 | 25 | 26 | def test_env_vars(config: TestConfig) -> List[Tuple[str, Any]]: 27 | return env_vars.cat(('RIBOSOME_LOG_FILE', str(config.log_file))) 28 | 29 | 30 | class TestNvimApi(RiboNvimApi): 31 | 32 | def __init__(self, name: str, comm: Comm, config: TestConfig) -> None: 33 | self.name = name 34 | self.comm = comm 35 | self.config = config 36 | 37 | 38 | class TestNvim(Dat['TestNvim']): 39 | 40 | def __init__(self, comm: Comm, api: NvimApi) -> None: 41 | self.comm = comm 42 | self.api = api 43 | 44 | 45 | @do(IO[None]) 46 | def setup_env(config: TestConfig) -> Do: 47 | yield test_env_vars(config).traverse2(set_env, IO) 48 | yield IO.delay(config.log_dir.mkdir, exist_ok=True, parents=True) 49 | yield IO.delay(config.log_file.touch) 50 | yield set_env('RIBOSOME_LOG_FILE', str(config.log_file)) 51 | 52 | 53 | def start_asio_embed(config: TestConfig) -> IO[NvimApi]: 54 | ''' start an embedded vim session that loads no init.vim. 55 | `_temp/log/vim` is set as log file. aside from being convenient, this is crucially necessary, as the first use of 56 | the session will block if stdout is used for output. 57 | ''' 58 | log = temp_dir('log') / 'vim' 59 | argv = nvim_cmdline + List('--embed', f'-V{log}') 60 | asio, rpc_comm = cons_asyncio_embed(argv) 61 | return start_external(config.config.basic.name, rpc_comm) 62 | 63 | 64 | def start_asio_socket(config: TestConfig, socket: Path) -> IO[NvimApi]: 65 | asio, rpc_comm = cons_asyncio_socket(socket) 66 | return start_external(config.config.basic.name, rpc_comm) 67 | 68 | 69 | @do(IO[TestNvim]) 70 | def setup_test_nvim_embed(config: TestConfig) -> Do: 71 | yield setup_env(config) 72 | api = yield start_asio_embed(config) 73 | return TestNvim(api.comm, TestNvimApi(api.name, api.comm, config)) 74 | 75 | 76 | @do(IO[TestNvim]) 77 | def setup_test_nvim_socket(config: TestConfig, socket: Path) -> Do: 78 | yield setup_env(config) 79 | api = yield start_asio_socket(config, socket) 80 | return TestNvim(api.comm, TestNvimApi(api.name, api.comm, config)) 81 | 82 | 83 | def set_nvim_vars(vars: Map[str, Any]) -> NvimIO[None]: 84 | return vars.to_list.traverse2(variable_set, NvimIO) 85 | 86 | 87 | __all__ = ('TestNvim', 'setup_test_nvim_embed', 'set_nvim_vars',) 88 | -------------------------------------------------------------------------------- /ribosome/test/integration/start.py: -------------------------------------------------------------------------------- 1 | from amino import do, Do 2 | from amino.json import dump_json 3 | 4 | from ribosome.nvim.io.compute import NvimIO 5 | from ribosome.test.config import TestConfig 6 | from ribosome.nvim.api.function import nvim_call_function 7 | from ribosome.config.config import Config 8 | from ribosome.nvim.io.api import N 9 | from ribosome.rpc.error import define_rpc_stderr_handler 10 | 11 | stderr_handler_prefix = 'RibosomeSpec' 12 | 13 | 14 | def start_plugin_cmd_import(path: str) -> NvimIO[str]: 15 | return N.pure(f'from ribosome.host import start_path; start_path({path!r})') 16 | 17 | 18 | @do(NvimIO[str]) 19 | def start_plugin_cmd_json(config: Config) -> Do: 20 | json = yield N.from_either(dump_json(config)) 21 | return f'from ribosome.host import start_json_config; start_json_config({json!r})' 22 | 23 | 24 | @do(NvimIO[None]) 25 | def start_plugin_embed(config: TestConfig) -> Do: 26 | stderr_handler_name = yield define_rpc_stderr_handler(stderr_handler_prefix) 27 | cmd = yield ( 28 | config.config_path.cata( 29 | start_plugin_cmd_import, 30 | lambda: start_plugin_cmd_json(config.config), 31 | ) 32 | ) 33 | args = ['python3', '-c', cmd] 34 | opts = dict(rpc=True, on_stderr=stderr_handler_name) 35 | yield nvim_call_function('jobstart', args, opts) 36 | 37 | 38 | __all__ = ('start_plugin_embed',) 39 | -------------------------------------------------------------------------------- /ribosome/test/klk/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /ribosome/test/klk/expectable.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Callable, Any 2 | 3 | from kallikrein import kf 4 | from kallikrein.expectable import Expectable 5 | 6 | from ribosome.nvim.api.data import NvimApi 7 | from ribosome.nvim.io.compute import NvimIO 8 | 9 | A = TypeVar('A') 10 | 11 | 12 | def kn(vim: NvimApi, f: Callable[..., NvimIO[A]], *a: Any, **kw: Any) -> Expectable: 13 | return kf(lambda: f(*a, **kw).result(vim)) 14 | 15 | 16 | def kns(vim: NvimApi, f: Callable[..., NvimIO[A]], *a: Any, **kw: Any) -> Expectable: 17 | return kf(lambda: f(*a, **kw).run_s(vim)) 18 | 19 | 20 | __all__ = ('kn', 'kns') 21 | -------------------------------------------------------------------------------- /ribosome/test/klk/expectation.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Any, TypeVar 2 | 3 | from kallikrein.expectation import LiftExpectationResult, ExpectationResult, FatalSpecResult 4 | from kallikrein import Expectation, k 5 | from kallikrein.matcher import Matcher 6 | 7 | from amino import do, Do 8 | 9 | from ribosome.nvim.io.compute import NvimIO 10 | from ribosome.nvim.api.util import nvimio_repeat_timeout 11 | from ribosome.nvim.io.api import N 12 | from ribosome.nvim.io.data import NError, NResult 13 | 14 | A = TypeVar('A') 15 | 16 | 17 | def failure_result(expectation: Callable[..., NvimIO[Expectation]], *a: Any, **kw: Any) -> NvimIO[ExpectationResult]: 18 | def failure_result(result: NError[Expectation]) -> NvimIO[ExpectationResult]: 19 | return expectation(*a, **kw) 20 | return failure_result 21 | 22 | 23 | @do(NvimIO[ExpectationResult]) 24 | def eval_expectation(expectation: Callable[..., NvimIO[Expectation]], *a: Any, **kw: Any) -> Do: 25 | exp = yield expectation(*a, **kw) 26 | yield N.from_io(exp.evaluate) 27 | 28 | 29 | def failed_expectation(result: NResult[A]) -> NvimIO[ExpectationResult]: 30 | return N.pure(FatalSpecResult('await_k', Exception(str(result)))) 31 | 32 | 33 | @do(NvimIO[Expectation]) 34 | def await_k( 35 | expectation: Callable[..., NvimIO[Expectation]], 36 | *a: Any, 37 | timeout: int=1, 38 | interval: float=.25, 39 | **kw: Any, 40 | ) -> Do: 41 | yield N.recover_error( 42 | nvimio_repeat_timeout( 43 | lambda: N.recover_failure(eval_expectation(expectation, *a, **kw), failed_expectation), 44 | lambda a: a.success, 45 | f'expectation not satisfied within {timeout} seconds', 46 | timeout, 47 | interval, 48 | ).map(LiftExpectationResult), 49 | failure_result(expectation, *a, **kw), 50 | ) 51 | 52 | 53 | @do(NvimIO[Expectation]) 54 | def await_k_with( 55 | matcher: Matcher[A], 56 | thunk: Callable[..., NvimIO[A]], 57 | *a: Any, 58 | timeout: int=1, 59 | interval: float=.25, 60 | **kw: Any, 61 | ) -> Do: 62 | yield await_k(lambda: thunk(*a, **kw).map(lambda a: k(a).must(matcher)), timeout=timeout, interval=interval) 63 | 64 | 65 | __all__ = ('await_k', 'await_k_with',) 66 | -------------------------------------------------------------------------------- /ribosome/test/klk/matchers/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /ribosome/test/klk/matchers/buffer.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from amino import List 4 | 5 | from ribosome.nvim.io.compute import NvimIO 6 | from ribosome.test.klk.expectation import await_k_with 7 | from ribosome.nvim.api.ui import current_buffer_content, buffers 8 | 9 | from kallikrein import Expectation 10 | from kallikrein.matchers.lines import have_lines 11 | from kallikrein.matchers.length import have_length 12 | from kallikrein.matcher import Matcher 13 | 14 | 15 | def current_buffer_matches(matcher: Matcher[List[str]]) -> NvimIO[Expectation]: 16 | return await_k_with(matcher, current_buffer_content) 17 | 18 | 19 | def current_buffer_contains(lines: Union[str, List[str]]) -> NvimIO[Expectation]: 20 | return current_buffer_matches(have_lines(lines)) 21 | 22 | 23 | def buffer_count_is(count: int) -> NvimIO[Expectation]: 24 | return await_k_with(have_length(count), buffers) 25 | 26 | 27 | __all__ = ('current_buffer_contains', 'buffer_count_is', 'current_buffer_matches',) 28 | -------------------------------------------------------------------------------- /ribosome/test/klk/matchers/command.py: -------------------------------------------------------------------------------- 1 | from amino import do, Do 2 | 3 | from ribosome.nvim.io.compute import NvimIO 4 | from ribosome.nvim.api.exists import command_exists, command_exists_not 5 | from ribosome.nvim.io.api import N 6 | 7 | from kallikrein import Expectation, k 8 | 9 | 10 | @do(NvimIO[Expectation]) 11 | def command_must_exist(name: str) -> Do: 12 | exists = yield command_exists(name) 13 | return k(exists).true 14 | 15 | 16 | @do(NvimIO[Expectation]) 17 | def command_must_not_exist(name: str) -> Do: 18 | exists_not = yield N.recover_failure(command_exists_not(name), lambda a: N.pure(False)) 19 | return k(exists_not).true 20 | 21 | 22 | __all__ = ('command_must_exist', 'command_must_not_exist',) 23 | -------------------------------------------------------------------------------- /ribosome/test/klk/matchers/nresult.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Callable 2 | 3 | from kallikrein.matcher import BoundMatcher, StrictMatcher 4 | from kallikrein.matchers.typed import ChainTyped, have_type 5 | from kallikrein.matchers import contain 6 | from kallikrein.matchers.contain import PredContain, NestContain 7 | from kallikrein.match_result import MatchResult, ContainsMatchResult 8 | 9 | from amino import Boolean 10 | from amino.case import Case 11 | 12 | from ribosome.nvim.io.data import NResult, NSuccess, NError, NFatal 13 | 14 | A = TypeVar('A') 15 | B = TypeVar('B') 16 | Result = Union[A, str, Exception] 17 | 18 | 19 | class apply_nresult(Generic[A, B], Case[NResult[A], B], alg=NResult): 20 | 21 | def __init__(self, target: Callable[[Result], B]) -> None: 22 | self.target = target 23 | 24 | def nsuccess(self, result: NSuccess[A]) -> B: 25 | return self.target(result.value) 26 | 27 | def nerror(self, result: NError[A]) -> B: 28 | return self.target(result.error) 29 | 30 | def nfatal(self, result: NFatal[A]) -> B: 31 | return self.target(result.exception) 32 | 33 | 34 | class PredContainNResult(PredContain, tpe=NResult): 35 | 36 | def check(self, exp: NResult[A], target: Result) -> Boolean: 37 | def match(a: Result) -> Boolean: 38 | return Boolean(a == target) 39 | apply: apply_nresult[A, Boolean] = apply_nresult(match) 40 | return apply(exp) 41 | 42 | 43 | class NestContainNResult(NestContain, tpe=NResult): 44 | 45 | def match(self, exp: NResult[A], target: BoundMatcher[Result]) -> MatchResult[Result]: 46 | apply: apply_nresult[A, MatchResult[Result]] = apply_nresult(target.evaluate) 47 | return apply(exp) 48 | 49 | def wrap(self, name: str, exp: NResult[A], nested: MatchResult[A]) -> MatchResult[A]: 50 | return ContainsMatchResult(name, exp, nested) 51 | 52 | 53 | class ChainTypedNResult(ChainTyped, tpe=NResult): 54 | 55 | def chain(self, matcher: StrictMatcher, other: Union[A, BoundMatcher]) -> BoundMatcher: 56 | return matcher & contain(other) 57 | 58 | 59 | nsuccess = have_type(NSuccess) 60 | nerror = have_type(NError) 61 | nfatal = have_type(NFatal) 62 | 63 | __all__ = ('nsuccess', 'nerror', 'nfatal') 64 | -------------------------------------------------------------------------------- /ribosome/test/klk/matchers/prog.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from ribosome.nvim.api.util import nvimio_repeat_timeout 4 | from ribosome.nvim.io.compute import NvimIO 5 | from ribosome.nvim.api.rpc import plugin_name 6 | from ribosome.nvim.api.function import nvim_call_json 7 | 8 | from amino import do, List, Do 9 | from amino.util.string import camelcase 10 | 11 | 12 | @do(NvimIO[List[str]]) 13 | def program_log() -> Do: 14 | name = yield plugin_name() 15 | yield nvim_call_json(f'{camelcase(name)}ProgramLog') 16 | 17 | 18 | def seen_program(name: str, timeout: float=1., interval=.25) -> NvimIO[None]: 19 | return nvimio_repeat_timeout( 20 | program_log, 21 | lambda a: a.contains(name), 22 | f'program `{name}` wasn\'t executed', 23 | timeout=timeout, 24 | interval=interval, 25 | ) 26 | 27 | 28 | @do(NvimIO[Any]) 29 | def plugin_state() -> Do: 30 | name = yield plugin_name() 31 | yield nvim_call_json(f'{camelcase(name)}State') 32 | 33 | 34 | @do(NvimIO[Any]) 35 | def component_state(component_name: str) -> Do: 36 | name = yield plugin_name() 37 | yield nvim_call_json(f'{camelcase(name)}ComponentState', component_name) 38 | 39 | 40 | __all__ = ('program_log', 'seen_program', 'plugin_state', 'component_state',) 41 | -------------------------------------------------------------------------------- /ribosome/test/klk/matchers/variable.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from kallikrein import Expectation, k 4 | 5 | from ribosome.nvim.io.compute import NvimIO 6 | from ribosome.nvim.io.api import N 7 | from ribosome.nvim.api.variable import var_becomes 8 | from ribosome.test.klk.matchers.nresult import nsuccess 9 | 10 | 11 | def var_must_become(name: str, value: Any, timeout: float=3, interval: float=.2) -> NvimIO[Expectation]: 12 | return N.intercept(var_becomes(name, value, timeout, interval), lambda r: N.pure(k(r).must(nsuccess(True)))) 13 | 14 | 15 | __all__ = ('var_must_become',) 16 | -------------------------------------------------------------------------------- /ribosome/test/klk/matchers/window.py: -------------------------------------------------------------------------------- 1 | from kallikrein import Expectation 2 | from kallikrein.matchers.comparison import eq 3 | 4 | from ribosome.test.klk.expectation import await_k_with 5 | from ribosome.nvim.io.compute import NvimIO 6 | from ribosome.nvim.api.ui import current_cursor 7 | 8 | 9 | def current_cursor_is(line: int, col: int) -> NvimIO[Expectation]: 10 | return await_k_with(eq((line, col)), current_cursor) 11 | 12 | 13 | __all__ = ('current_cursor_is',) 14 | -------------------------------------------------------------------------------- /ribosome/test/prog.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable 2 | from threading import Thread 3 | 4 | from amino import List, do, Do, Just, Lists, Map, Nil, IO, Either 5 | from amino.json import dump_json 6 | from amino.logging import module_log 7 | from amino.io import IOException 8 | 9 | from ribosome.rpc.api import RpcProgram 10 | from ribosome.nvim.io.state import NS 11 | from ribosome.data.plugin_state import PS 12 | from ribosome.rpc.to_plugin import run_program 13 | from ribosome.rpc.data.rpc import RpcArgs 14 | from ribosome.test.config import TestConfig 15 | from ribosome.rpc.state import cons_state 16 | from ribosome.nvim.io.api import N 17 | from ribosome.logging import nvim_logging 18 | from ribosome.nvim.io.compute import NvimIO 19 | from ribosome.components.internal.update import update_components 20 | from ribosome.nvim.io.data import NSuccess 21 | from ribosome import NvimApi 22 | 23 | log = module_log() 24 | 25 | 26 | def program_runner(args: List[Any]) -> Callable[[RpcProgram], NS[PS, Any]]: 27 | def runner(program: RpcProgram) -> NS[PS, Any]: 28 | return run_program(program, RpcArgs.cons(args)) 29 | return runner 30 | 31 | 32 | def no_matching_program(method: str) -> NS[PS, Any]: 33 | return NS.lift(N.error(f'no matching program for {method}')) 34 | 35 | 36 | @do(NS[PS, List[Any]]) 37 | def request(method: str, *args: Any, **json_args: Any) -> Do: 38 | progs = yield NS.inspect(lambda a: a.programs) 39 | matches = progs.filter(lambda a: a.rpc_name == method) 40 | json = yield NS.from_either(dump_json(Map(json_args))) 41 | json_arg = List(json) if json_args else Nil 42 | yield ( 43 | no_matching_program(method) 44 | if matches.empty else 45 | matches.traverse(program_runner(Lists.wrap(args) + json_arg), NS) 46 | ) 47 | 48 | 49 | @do(NS[PS, Any]) 50 | def request_one(method: str, *args: Any) -> Do: 51 | results = yield request(method, *args) 52 | yield NS.m(results.head, lambda: f'empty result list for request `{method}`') 53 | 54 | 55 | def fork_request(method: str, *args: Any, **json_args: Any) -> NS[PS, Either[IOException, Thread]]: 56 | def run(s: PS, v: NvimApi) -> None: 57 | try: 58 | result = request(method, *args, **json_args).run(s).run_a(v) 59 | if not isinstance(result, NSuccess): 60 | log.error(result) 61 | except Exception as e: 62 | log.error(e) 63 | return NS.apply(lambda s: N.delay(lambda v: (s, IO.fork(run, s, v, daemon=True).attempt))) 64 | 65 | 66 | @do(NvimIO[PS]) 67 | def init_test_state(config: TestConfig) -> Do: 68 | log_handler = yield N.delay(nvim_logging) 69 | state = cons_state(config.config, config.io_interpreter, config.logger, log_handler=log_handler) 70 | yield update_components(Just(config.components)).nvim.run_s(state) 71 | 72 | 73 | __all__ = ('program_runner', 'request', 'init_test_state', 'request_one', 'fork_request',) 74 | -------------------------------------------------------------------------------- /ribosome/test/request.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Tuple, Any 2 | 3 | from amino import Map, List, Either, Left, Right, do, Do 4 | 5 | from ribosome.nvim.api.data import NvimApi, StrictNvimApi 6 | 7 | Handler = Callable[['StrictNvimApi', str, List[Any]], Either[str, Tuple[NvimApi, Any]]] 8 | 9 | 10 | def api_info(vim: NvimApi, name: str, args: List[Any]) -> Either[str, Tuple[NvimApi, Any]]: 11 | return Right((vim, (1, {}))) 12 | 13 | 14 | def rh_write(vim: NvimApi, name: str, args: List[Any]) -> Either[str, Tuple[NvimApi, Any]]: 15 | return Right((vim, None)) 16 | 17 | 18 | @do(Either[str, Tuple[NvimApi, Any]]) 19 | def default_request_handler(vim: NvimApi, name: str, args: List[Any]) -> Do: 20 | handler = yield default_request_handlers.lift(name).to_either(f'no default request handler for {name}') 21 | yield handler(vim, name, args) 22 | 23 | 24 | @do(Either[str, Tuple[NvimApi, Any]]) 25 | def pop_first(vim: NvimApi, name: str, args: List[Any]) -> Do: 26 | name1, args1 = yield args.uncons.to_either(f'invalid command: {name}({args})') 27 | yield default_request_handler(vim, name1, args1) 28 | 29 | 30 | def rh_atomic(vim: NvimApi, name: str, args: List[Any]) -> Either[str, Tuple[NvimApi, Any]]: 31 | count = args.head.map(len).get_or_strict(0) 32 | return Right((vim, [None] * count)) 33 | 34 | 35 | default_request_handlers = Map({ 36 | 'silent': pop_first, 37 | 'silent!': pop_first, 38 | 'nvim_get_api_info': api_info, 39 | 'nvim_command': rh_write, 40 | 'nvim_out_write': rh_write, 41 | 'nvim_call_function': pop_first, 42 | 'nvim_call_atomic': rh_atomic, 43 | }) 44 | 45 | 46 | @do(Either[str, Tuple[NvimApi, Any]]) 47 | def specific_handler(desc: str, handler: Handler, vim: StrictNvimApi, name: str, args: List[Any]) -> Do: 48 | name1, args1 = yield args.uncons.to_either(f'empty {desc} args: {name}') 49 | yield handler(vim, name1, args1) 50 | 51 | 52 | @do(Either[str, Tuple[NvimApi, Any]]) 53 | def function_handler(handler: Handler, vim: StrictNvimApi, name: str, args: List[Any]) -> Do: 54 | yield specific_handler('function call', handler, vim, name, args) 55 | 56 | 57 | @do(Either[str, Tuple[NvimApi, Any]]) 58 | def command_handler(handler: Handler, vim: StrictNvimApi, name: str, args: List[Any]) -> Do: 59 | yield specific_handler('command', handler, vim, name, args) 60 | 61 | 62 | class StrictRequestHandler: 63 | 64 | def __init__( 65 | self, 66 | extra: Handler, 67 | function_handler: Handler, 68 | command_handler: Handler, 69 | ) -> None: 70 | self.extra = extra 71 | self.function_handler = function_handler 72 | self.command_handler = command_handler 73 | 74 | def __call__(self, vim: NvimApi, name: str, args: List[Any], sync: bool) -> Either[List[str], Tuple[NvimApi, Any]]: 75 | return ( 76 | self.extra(vim, name, args) 77 | .lmap(List) 78 | .accum_error_lift(function_handler, self.function_handler, vim, name, args) 79 | .accum_error_lift(command_handler, self.command_handler, vim, name, args) 80 | .accum_error_lift(default_request_handler, vim, name, args) 81 | ) 82 | 83 | 84 | def no_handler(vim: NvimApi, name: str, args: List[Any]) -> Either[str, Tuple[NvimApi, Any]]: 85 | return Left(f'no handler for {name}') 86 | 87 | 88 | __all__ = ('StrictRequestHandler', 'no_handler', 'Handler',) 89 | -------------------------------------------------------------------------------- /ribosome/test/rpc.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | import json 3 | 4 | from amino import List, Lists 5 | 6 | from ribosome.nvim.io.compute import NvimIO, NRParams 7 | from ribosome.nvim.api.command import nvim_command 8 | 9 | 10 | def format_json_cmd(args: List[str], data: dict) -> str: 11 | j = json.dumps(data) 12 | return f'{args.join_tokens} {j}' 13 | 14 | 15 | def json_cmd(cmd: str, *args: str, **data: Any) -> NvimIO[str]: 16 | return nvim_command(cmd, format_json_cmd(Lists.wrap(args), data), params=NRParams.cons(verbose=True, sync=False)) 17 | 18 | 19 | __all__ = ('json_cmd',) 20 | -------------------------------------------------------------------------------- /ribosome/test/run.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable 2 | 3 | from kallikrein import Expectation, k 4 | from kallikrein.matchers.either import be_right 5 | from kallikrein.matchers.match_with import match_with 6 | 7 | from amino import IO 8 | 9 | from ribosome.nvim.io.compute import NvimIO 10 | from ribosome.nvim.api.variable import pvar_becomes 11 | 12 | 13 | def run_test_io(io: Callable[..., IO[Expectation]], *a: Any, **kw: Any) -> Expectation: 14 | return k(io(*a, **kw).attempt).must(be_right(match_with(lambda a: a))) 15 | 16 | 17 | def plugin_started() -> NvimIO[None]: 18 | return pvar_becomes('started', True, timeout=5) 19 | 20 | 21 | __all__ = ('run_test_io', 'plugin_started',) 22 | -------------------------------------------------------------------------------- /ribosome/test/unit.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Any 2 | 3 | from kallikrein import Expectation 4 | 5 | from amino import Do, do 6 | from amino.lenses.lens import lens 7 | 8 | from ribosome.nvim.io.compute import NvimIO 9 | from ribosome.test.config import TestConfig 10 | from ribosome.nvim.api.data import StrictNvimApi 11 | from ribosome.test.run import run_test_io 12 | from ribosome.nvim.io.api import N 13 | from ribosome.test.prog import init_test_state 14 | from ribosome.nvim.io.state import NS 15 | from ribosome.data.plugin_state import PS 16 | from ribosome.test.request import StrictRequestHandler 17 | 18 | 19 | def setup_strict_test_nvim(config: TestConfig) -> StrictNvimApi: 20 | handler = StrictRequestHandler(config.request_handler, config.function_handler, config.command_handler) 21 | return StrictNvimApi.cons(config.config.basic.name, vars=config.vars, request_handler=handler) 22 | 23 | 24 | def unit_test(config: TestConfig, io: Callable[..., NS[PS, Expectation]], *a: Any, **kw: Any) -> Expectation: 25 | initial_nvim = setup_strict_test_nvim(config) 26 | @do(NvimIO[Expectation]) 27 | def run() -> Do: 28 | state = yield init_test_state(config) 29 | yield io(*a, **kw).run_a(state) 30 | return run_test_io(N.to_io_a, run(), initial_nvim) 31 | 32 | 33 | def update_data(**kw: Any) -> NS[PS, None]: 34 | return NS.modify(lens.data.modify(lambda a: a.copy(**kw))) 35 | 36 | 37 | __all__ = ('unit_test', 'update_data',) 38 | -------------------------------------------------------------------------------- /ribosome/test/unite.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | from typing import Callable, Any 3 | 4 | from amino import env 5 | 6 | 7 | def unite(f: Callable[[Any, str], None]) -> Callable[[Any], None]: 8 | @wraps(f) 9 | def wrapper(self: Any) -> None: 10 | def go(unite: str) -> Any: 11 | self.vim.options.amend_l('rtp', [unite]) 12 | self.vim.cmd('source {}/plugin/*.vim'.format(unite)) 13 | self.vim.cmd('source {}/plugin/unite/*.vim'.format(unite)) 14 | self.vim.cmd('source {}/syntax/*.vim'.format(unite)) 15 | return f(self) 16 | return env['UNITE_DIR'] / go | None 17 | return wrapper 18 | 19 | __all__ = ('unite',) 20 | -------------------------------------------------------------------------------- /ribosome/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tek/ribosome-py/8bd22e549ddff1ee893d6e3a0bfba123a09e96c6/ribosome/util/__init__.py -------------------------------------------------------------------------------- /ribosome/util/callback.py: -------------------------------------------------------------------------------- 1 | import re 2 | import abc 3 | from typing import Callable 4 | 5 | from amino import Maybe, Either, __, Right, L, List, _, Map, Try 6 | 7 | from ribosome.logging import Logging 8 | from ribosome.nvim.api.data import NvimApi 9 | 10 | 11 | class VimCallback(Logging, metaclass=abc.ABCMeta): 12 | 13 | def __init__(self, target) -> None: 14 | self.target = target 15 | self.vim = self.target.root 16 | 17 | @abc.abstractmethod 18 | def __call__(self, data): 19 | ... 20 | 21 | 22 | class SpecialCallback: 23 | 24 | def __init__(self, name) -> None: 25 | self.name = name 26 | 27 | def __str__(self): 28 | return self.name 29 | 30 | 31 | class CallbackSpec(Logging, metaclass=abc.ABCMeta): 32 | 33 | def __init__(self, spec) -> None: 34 | self.spec = spec 35 | 36 | @abc.abstractmethod 37 | def func(self, vim: NvimApi) -> Either[str, Callable]: 38 | ... 39 | 40 | def __call__(self, vim: NvimApi, *a): 41 | return self.func(vim) // __(*a) 42 | 43 | def __str__(self): 44 | return '{}({})'.format(self.__class__.__name__, self.spec) 45 | 46 | 47 | class PythonCallbackSpecBase(CallbackSpec): 48 | 49 | def _inst(self, vim, name): 50 | return (Try(name, vim) 51 | if isinstance(name, type) and issubclass(name, VimCallback) 52 | else Right(name)) 53 | 54 | @abc.abstractmethod 55 | def _func(self): 56 | ... 57 | 58 | def func(self, vim): 59 | return self._func // L(self._inst)(vim, _) 60 | 61 | 62 | class PythonCallbackSpec(PythonCallbackSpecBase): 63 | 64 | @property 65 | def _func(self): 66 | return Either.import_path(self.spec) 67 | 68 | 69 | class StrictPythonCallbackSpec(PythonCallbackSpecBase): 70 | 71 | @property 72 | def _func(self): 73 | return Right(self.spec) 74 | 75 | 76 | class VimFuncCallbackSpec(CallbackSpec): 77 | 78 | def func(self, vim): 79 | return Right(L(vim.call)(self.spec)) 80 | 81 | 82 | class VarCallbackSpec(CallbackSpec): 83 | 84 | def func(self, vim): 85 | return Right(L(vim.vars)(self.spec)) 86 | 87 | _py_callback_re = re.compile('^py:(.+)') 88 | _vim_callback_re = re.compile('^vim:(.+)') 89 | _var_callback_re = re.compile('^var:(.+)') 90 | _special_callback_re = re.compile('^s:(.+)') 91 | 92 | _callback_res = List( 93 | (_py_callback_re, PythonCallbackSpec), 94 | (_vim_callback_re, VimFuncCallbackSpec), 95 | (_var_callback_re, VarCallbackSpec), 96 | ) 97 | 98 | 99 | def _cb_err(data): 100 | return 'invalid callback string: {}'.format(data) 101 | 102 | 103 | def parse_callback(data: str, rex, tpe: type): 104 | return ( 105 | Maybe(rex.match(data)) / 106 | __.group(1) / 107 | tpe 108 | ) 109 | 110 | 111 | def parse_special_callback(data, special: Map) -> Maybe[CallbackSpec]: 112 | return ( 113 | Maybe(_special_callback_re.match(data)) / 114 | __.group(1) // 115 | special.get / 116 | StrictPythonCallbackSpec 117 | ) 118 | 119 | 120 | def parse_callback_spec(data: str, special=Map()) -> Maybe[CallbackSpec]: 121 | spec = special.empty.no.flat_m(L(parse_special_callback)(data, special)) 122 | other = lambda: _callback_res.flat_map2(L(parse_callback)(data, _, _)).head 123 | return spec.o(other).to_either(_cb_err(data)) 124 | 125 | __all__ = ('VimCallback', 'parse_callback_spec') 126 | -------------------------------------------------------------------------------- /ribosome/util/doc/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /ribosome/util/doc/format.py: -------------------------------------------------------------------------------- 1 | from amino.case import Case 2 | from amino import do, Do, Dat, Just, Nothing 3 | from amino.state import State 4 | from amino.util.string import camelcase 5 | 6 | from ribosome.util.doc.data import Anchor, VariableAnchor, AnchorType, MappingAnchor, RpcAnchor, GeneralAnchor 7 | from ribosome.rpc.data.prefix_style import Short, Full 8 | 9 | 10 | class CompilerConfig(Dat['CompilerConfig']): 11 | 12 | @staticmethod 13 | def cons( 14 | name: str, 15 | prefix: str=None, 16 | ) -> 'CompilerConfig': 17 | return CompilerConfig( 18 | name, 19 | prefix or name, 20 | ) 21 | 22 | def __init__(self, name: str, prefix: str) -> None: 23 | self.name = name 24 | self.prefix = prefix 25 | 26 | 27 | def format_variable(name: str, anchor: Anchor, tpe: VariableAnchor) -> str: 28 | prefix = f'{name}_' if tpe.prefix else '' 29 | return f'{tpe.scope}:{prefix}{anchor.text}' 30 | 31 | 32 | class compile_anchor_type(Case[AnchorType, State[CompilerConfig, str]], alg=AnchorType): 33 | 34 | def __init__(self, anchor: Anchor) -> None: 35 | self.anchor = anchor 36 | 37 | @do(State[CompilerConfig, str]) 38 | def variable(self, a: VariableAnchor) -> Do: 39 | name = yield State.inspect(lambda a: a.name) 40 | return format_variable(name, self.anchor, a) 41 | 42 | def mapping(self, a: MappingAnchor) -> State[CompilerConfig, str]: 43 | return State.pure('') 44 | 45 | @do(State[CompilerConfig, str]) 46 | def rpc(self, a: RpcAnchor) -> Do: 47 | name = yield State.inspect(lambda a: a.name) 48 | prefix = yield State.inspect(lambda a: a.prefix) 49 | rpc_prefix = Just(name) if a.prefix == Full() else Just(prefix) if a.prefix == Short() else Nothing 50 | prefix_s = rpc_prefix.map(lambda a: f'{a}_').get_or_strict('') 51 | cc = camelcase(f'{prefix_s}{self.anchor.text}') 52 | return f':{cc}' 53 | 54 | def general(self, a: GeneralAnchor) -> State[CompilerConfig, str]: 55 | return State.pure(self.anchor.text) 56 | 57 | 58 | def compile_anchor(anchor: Anchor) -> State[CompilerConfig, str]: 59 | return compile_anchor_type(anchor)(anchor.tpe) 60 | 61 | 62 | __all__ = ('format_variable', 'compile_anchor',) 63 | -------------------------------------------------------------------------------- /ribosome/util/doc/write.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | from types import ModuleType 3 | 4 | from amino.logging import module_log 5 | from amino import List, IO, do, Do, Path 6 | from amino.io import IOException 7 | 8 | from ribosome.util.doc.data import StaticDoc, DocCompiler 9 | from ribosome.util.doc.markdown import markdown_compiler, MarkdownCompilerConfig 10 | from ribosome.util.doc.vim import vim_compiler, VimCompilerConfig 11 | from ribosome.util.doc.generate import generate_plugin_doc 12 | from ribosome.util.doc.format import CompilerConfig 13 | 14 | log = module_log() 15 | A = TypeVar('A') 16 | B = TypeVar('B') 17 | 18 | 19 | def report_error(error: IOException) -> None: 20 | log.error(f'failed to generate doc: {error}') 21 | 22 | 23 | @do(IO[None]) 24 | def run( 25 | components: str, 26 | settings: List[ModuleType], 27 | compiler: DocCompiler[A, B], 28 | outfile: Path, 29 | static: StaticDoc, 30 | ) -> Do: 31 | text = yield generate_plugin_doc(components, settings, static, compiler) 32 | yield IO.delay(outfile.write_text, text.join_lines) 33 | 34 | 35 | @do(IO[None]) 36 | def run_default( 37 | components: str, 38 | settings: List[ModuleType], 39 | pkg_dir: Path, 40 | name: str, 41 | prefix: str, 42 | static: StaticDoc, 43 | ) -> Do: 44 | vim_conf = VimCompilerConfig.cons(CompilerConfig.cons(name, prefix)) 45 | md_conf = MarkdownCompilerConfig.cons(CompilerConfig.cons(name, prefix)) 46 | yield run(components, settings, markdown_compiler(md_conf), pkg_dir / 'README.md', static) 47 | yield run(components, settings, vim_compiler(vim_conf), pkg_dir / 'data' / 'runtime' / f'{name}.txt', static) 48 | 49 | 50 | def write_default_docs( 51 | components: str, 52 | settings: List[ModuleType], 53 | pkg_dir: Path, 54 | name: str, 55 | prefix: str, 56 | static: StaticDoc, 57 | ) -> None: 58 | run_default(components, settings, pkg_dir, name, prefix, static).attempt.lmap(report_error) 59 | 60 | __all__ = ('write_default_docs',) 61 | -------------------------------------------------------------------------------- /ribosome/util/menu/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /ribosome/util/menu/auto/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /ribosome/util/menu/auto/cmd.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | 3 | from amino import do, Do, Map, List, Maybe 4 | from amino.lenses.lens import lens 5 | from amino.logging import module_log 6 | 7 | from ribosome.util.menu.auto.data import AutoS 8 | from ribosome.nvim.io.state import NS 9 | from ribosome.util.menu.data import MenuPrompt, MenuUpdateCursor, MenuLine, visible_menu_indexes, MenuState, MenuQuit 10 | from ribosome.util.menu.prompt.data import PromptPassthrough, InputState 11 | from ribosome.util.menu.prompt.run import prompt_state_fork_strict 12 | 13 | log = module_log() 14 | A = TypeVar('A') 15 | S = TypeVar('S') 16 | ML = TypeVar('ML') 17 | U = TypeVar('U') 18 | 19 | 20 | def menu_cmd_esc() -> AutoS[S, ML, U]: 21 | return prompt_state_fork_strict( 22 | MenuPrompt(PromptPassthrough()), 23 | MenuQuit(), 24 | ) 25 | 26 | 27 | @do(NS[InputState[MenuState[S, ML, U], U], int]) 28 | def menu_direction() -> Do: 29 | bottom = yield NS.inspect(lambda a: a.data.config.bottom) 30 | return -1 if bottom else 1 31 | 32 | 33 | @do(AutoS[S, ML, U]) 34 | def menu_cmd_scroll(offset: int) -> Do: 35 | direction = yield menu_direction() 36 | visible_indexes = yield NS.inspect(lambda a: visible_menu_indexes(a.data.content)) 37 | count = visible_indexes.length 38 | yield NS.modify(lens.data.cursor.modify(lambda a: (a + direction * offset) % count)) 39 | return MenuUpdateCursor() 40 | 41 | 42 | def menu_cmd_up() -> AutoS[S, ML, U]: 43 | return menu_cmd_scroll(-1) 44 | 45 | 46 | def menu_cmd_down() -> AutoS[S, ML, U]: 47 | return menu_cmd_scroll(1) 48 | 49 | 50 | def toggle_selected(lines: List[MenuLine[A]], index: int) -> Maybe[List[MenuLine[A]]]: 51 | return lines.modify_at(index, lambda b: b.mod.selected(lambda c: not c)) 52 | 53 | 54 | visibility_error = 'broken visibility map for menu' 55 | 56 | 57 | @do(AutoS[S, ML, U]) 58 | def menu_cmd_select_cursor() -> Do: 59 | cursor = yield NS.inspect(lambda a: a.cursor) 60 | content = yield NS.inspect(lambda a: a.data.content) 61 | visible_indexes = visible_menu_indexes(content) 62 | index = yield NS.from_maybe(visible_indexes.lift(cursor), lambda: visibility_error) 63 | toggled = yield NS.from_maybe(toggle_selected(content.lines, index), lambda: visibility_error) 64 | yield NS.modify(lens.data.content.lines.set(toggled)) 65 | return MenuUpdateCursor() 66 | 67 | 68 | @do(AutoS[S, ML, U]) 69 | def menu_cmd_select_all() -> Do: 70 | yield NS.modify(lens.data.content.lines.modify(lambda a: a.map(lambda b: b.mod.selected(lambda c: not c)))) 71 | return MenuUpdateCursor() 72 | 73 | 74 | builtin_mappings = Map({ 75 | '': menu_cmd_esc, 76 | 'q': menu_cmd_esc, 77 | 'k': menu_cmd_up, 78 | '': menu_cmd_up, 79 | 'j': menu_cmd_down, 80 | '': menu_cmd_down, 81 | '': menu_cmd_select_cursor, 82 | '*': menu_cmd_select_all, 83 | }) 84 | 85 | __all__ = ('builtin_mappings',) 86 | -------------------------------------------------------------------------------- /ribosome/util/menu/auto/data.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Callable, Generic, Any 2 | 3 | from amino import ADT, List, Dat, Map 4 | 5 | from ribosome.util.menu.data import MenuAction, MenuState, MenuLine 6 | from ribosome.util.menu.prompt.data import InputState, PromptConsumerUpdate 7 | from ribosome.nvim.io.state import NS 8 | 9 | S = TypeVar('S') 10 | ML = TypeVar('ML') 11 | U = TypeVar('U') 12 | MenuS = NS[InputState[MenuState[S, ML, U], U], MenuAction] 13 | 14 | 15 | class AutoUpdate(Generic[U, ML], ADT['AutoUpdate[U, ML]']): 16 | pass 17 | 18 | 19 | class AutoUpdateRefresh(AutoUpdate[U, ML]): 20 | 21 | def __init__(self, lines: List[MenuLine[ML]]) -> None: 22 | self.lines = lines 23 | 24 | 25 | class AutoUpdateConsumer(AutoUpdate[U, ML]): 26 | 27 | def __init__(self, data: U) -> None: 28 | self.data = data 29 | 30 | 31 | class AutoState(Generic[S, ML, U], Dat['AutoState[S, ML, U]']): 32 | 33 | def __init__( 34 | self, 35 | consumer: Callable[[PromptConsumerUpdate[AutoUpdate[U, ML]]], 36 | NS[InputState[MenuState[Any, ML, U], AutoUpdate[U, ML]], MenuAction]], 37 | state: S, 38 | mappings: Map[str, Callable[[], MenuS[S, AutoUpdate[U, ML], ML]]], 39 | ) -> None: 40 | self.consumer = consumer 41 | self.state = state 42 | self.mappings = mappings 43 | 44 | 45 | AutoS = MenuS[AutoState[S, ML, U], ML, AutoUpdate[U, ML]] 46 | 47 | __all__ = ('S', 'ML', 'U', 'MenuS', 'AutoUpdate', 'AutoUpdateRefresh', 'AutoUpdateConsumer', 'AutoState', 'AutoS',) 48 | -------------------------------------------------------------------------------- /ribosome/util/menu/codes.py: -------------------------------------------------------------------------------- 1 | from amino import List, Map 2 | 3 | special_codes = Map({ 4 | b'\x80\xffX': 'c-@', 5 | b'\x80kb': 'bs', 6 | 9: 'tab', 7 | b'\x80kB': 's-tab', 8 | 10: 'c-j', 9 | 11: 'c-k', 10 | 12: 'fe', 11 | 13: 'cr', 12 | 27: 'esc', 13 | 32: 'space', 14 | 60: 'lt', 15 | 92: 'bslash', 16 | 124: 'bar', 17 | b'\x0b': 'c-k', 18 | b'\x80kD': 'del', 19 | b'\x9B': 'csi', 20 | b'\x80\xfdP': 'xcsi', 21 | b'\x80ku': 'up', 22 | b'\x80kd': 'down', 23 | b'\x80kl': 'left', 24 | b'\x80kr': 'right', 25 | b'\x80\xfd': 's-up', 26 | b'\x80\xfd': 's-down', 27 | b'\x80#4': 's-left', 28 | b'\x80%i': 's-right', 29 | b'\x80\xfdT': 'c-left', 30 | b'\x80\xfdU': 'c-right', 31 | b'\x80k1': 'f1', 32 | b'\x80k2': 'f2', 33 | b'\x80k3': 'f3', 34 | b'\x80k4': 'f4', 35 | b'\x80k5': 'f5', 36 | b'\x80k6': 'f6', 37 | b'\x80k7': 'f7', 38 | b'\x80k8': 'f8', 39 | b'\x80k9': 'f9', 40 | b'\x80k;': 'f10', 41 | b'\x80F1': 'f11', 42 | b'\x80F2': 'f12', 43 | b'\x80\xfd\x06': 's-f1', 44 | b'\x80\xfd\x07': 's-f2', 45 | b'\x80\xfd\x08': 's-f3', 46 | b'\x80\xfd\x09': 's-f4', 47 | b'\x80\xfd\x0A': 's-f5', 48 | b'\x80\xfd\x0B': 's-f6', 49 | b'\x80\xfd\x0C': 's-f7', 50 | b'\x80\xfd\x0D': 's-f8', 51 | b'\x80\xfd\x0E': 's-f9', 52 | b'\x80\xfd\x0F': 's-f10', 53 | b'\x80\xfd\x10': 's-f11', 54 | b'\x80\xfd\x11': 's-f12', 55 | b'\x80%1': 'help', 56 | b'\x80&8': 'undo', 57 | b'\x80kI': 'insert', 58 | b'\x80kh': 'home', 59 | b'\x80@7': 'end', 60 | b'\x80kP': 'pageup', 61 | b'\x80kN': 'pagedown', 62 | b'\x80K1': 'khome', 63 | b'\x80K4': 'kend', 64 | b'\x80K3': 'kpageup', 65 | b'\x80K5': 'kpagedown', 66 | b'\x80K6': 'kplus', 67 | b'\x80K7': 'kminus', 68 | b'\x80K9': 'kmultiply', 69 | b'\x80K8': 'kdivide', 70 | b'\x80KA': 'kenter', 71 | b'\x80KB': 'kpoint', 72 | b'\x80KC': 'k0', 73 | b'\x80KD': 'k1', 74 | b'\x80KE': 'k2', 75 | b'\x80KF': 'k3', 76 | b'\x80KG': 'k4', 77 | b'\x80KH': 'k5', 78 | b'\x80KI': 'k6', 79 | b'\x80KJ': 'k7', 80 | b'\x80KK': 'k8', 81 | b'\x80KL': 'k9', 82 | }) 83 | 84 | 85 | modifier_codes = List( 86 | (2, 'shift'), 87 | (4, 'control'), 88 | (8, 'alt'), 89 | (16, 'meta'), 90 | (32, 'mouse_double'), 91 | (64, 'mouse_triple'), 92 | (96, 'mouse_quadruple'), 93 | (128, 'command'), 94 | ) 95 | 96 | __all__ = ('special_codes', 'modifier_codes',) 97 | -------------------------------------------------------------------------------- /ribosome/util/menu/prompt/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /ribosome/util/menu/prompt/input.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Any, Generic 2 | 3 | from amino import do, Do, IO, Right, Either, List, Nil, Maybe, Try, Left 4 | from amino.logging import module_log 5 | from amino.case import Case 6 | 7 | from ribosome.nvim.api.function import nvim_call_cons, nvim_call_tpe 8 | from ribosome.nvim.io.compute import NvimIO, NRParams 9 | from ribosome.nvim.io.api import N 10 | from ribosome.util.menu.codes import modifier_codes, special_codes 11 | from ribosome.util.menu.prompt.data import (Input, NoInput, InputResources, InterruptInput, NormalInput, SpecialChar, 12 | PrintableChar, PromptInput) 13 | from ribosome.util.menu.prompt.interrupt import intercept_interrupt, stop_prompt 14 | 15 | log = module_log() 16 | A = TypeVar('A') 17 | B = TypeVar('B') 18 | 19 | 20 | @do(NvimIO[List[str]]) 21 | def input_modifiers() -> Do: 22 | code = yield nvim_call_tpe(int, 'getcharmod') 23 | return ( 24 | Nil 25 | if code == 0 else 26 | modifier_codes.filter2(lambda c, n: code % c == 0).map2(lambda c, n: n) 27 | ) 28 | 29 | 30 | def parse_special(key: Any) -> Maybe[str]: 31 | return special_codes.lift(key).map(lambda a: f'<{a}>') 32 | 33 | 34 | def parse_printable(key: Any) -> Maybe[str]: 35 | return Try(chr, key).or_else_call(Try, ord, key).to_maybe 36 | 37 | 38 | def parse_key(key: Any) -> Maybe[Either[str, str]]: 39 | return ( 40 | parse_special(key).map(Left) 41 | .or_else_call(lambda: parse_printable(key).map(Right)) 42 | ) 43 | 44 | 45 | @do(NvimIO[Either[str, Input]]) 46 | def analyse_key(key: Either[str, str]) -> Do: 47 | log.debug(f'received prompt input `{key.value}`') 48 | modifiers = yield input_modifiers() 49 | return Right(NormalInput(key.cata(lambda a: SpecialChar(a, modifiers), PrintableChar))) 50 | 51 | 52 | def parse_nonnull_input(data: Any) -> NvimIO[Either[str, Input]]: 53 | return parse_key(data).map(analyse_key).get_or(Left, f'could not parse input `{data}`') 54 | 55 | 56 | def parse_input(data: Any) -> NvimIO[Either[str, Input]]: 57 | return ( 58 | N.pure(Right(NoInput())) 59 | if data == 0 else 60 | parse_nonnull_input(data) 61 | ) 62 | 63 | 64 | def getchar() -> NvimIO[Input]: 65 | return nvim_call_cons(parse_input, 'getchar', False, params=NRParams.cons(decode=False, timeout=120)) 66 | 67 | 68 | class process_input(Generic[A, B], Case[Input, NvimIO[None]], alg=Input): 69 | 70 | def __init__(self, res: InputResources[A, B]) -> None: 71 | self.res = res 72 | 73 | @do(NvimIO[None]) 74 | def no(self, a: NoInput) -> Do: 75 | yield N.sleep(self.res.interval) 76 | yield input_loop_unsafe(self.res) 77 | 78 | @do(NvimIO[None]) 79 | def normal(self, a: NormalInput) -> Do: 80 | yield N.from_io(IO.delay(self.res.inputs.put, PromptInput(a.char))) 81 | yield input_loop_unsafe(self.res) 82 | 83 | def interrupt(self, a: InterruptInput) -> NvimIO[None]: 84 | return stop_prompt(self.res.inputs, self.res.stop) 85 | 86 | 87 | @do(NvimIO[None]) 88 | def input_step(res: InputResources[A, B]) -> Do: 89 | input = yield getchar() 90 | yield process_input(res)(input) 91 | 92 | 93 | def input_loop_unsafe(res: InputResources[A, B]) -> NvimIO[None]: 94 | return N.unit if res.stop.is_set() else input_step(res) 95 | 96 | 97 | def input_loop(res: InputResources[A, B]) -> NvimIO[None]: 98 | return intercept_interrupt(res.inputs, res.stop, None, input_loop_unsafe(res)) 99 | 100 | 101 | __all__ = ('input_loop',) 102 | -------------------------------------------------------------------------------- /ribosome/util/menu/prompt/interrupt.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, TypeVar 2 | from threading import Event 3 | from queue import Queue 4 | 5 | from amino import do, Do, IO 6 | 7 | from ribosome.nvim.io.compute import NvimIO, lift_n_result 8 | from ribosome.nvim.io.api import N 9 | from ribosome.nvim.io.data import NResult, NError 10 | from ribosome.util.menu.prompt.data import PromptInterrupt, InputResources 11 | from ribosome.nvim.io.state import NS 12 | 13 | A = TypeVar('A') 14 | B = TypeVar('B') 15 | 16 | 17 | @do(IO[None]) 18 | def stop_prompt(queue: Queue, stop: Event) -> Do: 19 | yield IO.delay(stop.set) 20 | yield IO.delay(queue.put, PromptInterrupt()) 21 | 22 | 23 | @do(NS[InputResources[A, B], None]) 24 | def stop_prompt_s() -> Do: 25 | res = yield NS.get() 26 | yield NS.lift(N.from_io(stop_prompt(res.inputs, res.stop))) 27 | 28 | 29 | def intercept_interrupt_result(queue: Queue, stop: Event, default: A) -> Callable[[NResult[A]], NvimIO[A]]: 30 | @do(NvimIO[A]) 31 | def intercept_interrupt_result(result: NResult[A]) -> Do: 32 | yield N.from_io(stop_prompt(queue, stop)) 33 | yield ( 34 | N.pure(default) 35 | if isinstance(result, NError) and 'Keyboard interrupt' in str(result.error) else 36 | lift_n_result.match(result) 37 | ) 38 | return intercept_interrupt_result 39 | 40 | 41 | def intercept_interrupt(queue: Queue, stop: Event, default: A, thunk: NvimIO[A]) -> NvimIO[A]: 42 | return N.recover_failure(thunk, intercept_interrupt_result(queue, stop, default)) 43 | 44 | 45 | __all__ = ('stop_prompt', 'intercept_interrupt', 'stop_prompt_s',) 46 | -------------------------------------------------------------------------------- /ribosome/util/persist.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Callable 2 | 3 | from lenses import UnboundLens 4 | 5 | from amino import IO, Path, Left, do, Do, Either, Right 6 | from amino.json import decode_json, dump_json 7 | from amino.logging import module_log 8 | from amino.util.fs import mkdir 9 | 10 | from ribosome.nvim.io.compute import NvimIO 11 | from ribosome.nvim.io.state import NS 12 | from ribosome.nvim.io.api import N 13 | from ribosome.config.settings import project_state_dir 14 | 15 | log = module_log() 16 | A = TypeVar('A') 17 | D = TypeVar('D') 18 | 19 | 20 | @do(NvimIO[Path]) 21 | def check_state_dir(dir: Path, name: str) -> Do: 22 | yield N.from_io(mkdir(dir)) 23 | yield N.pure(dir / f'{name}.json') 24 | 25 | 26 | @do(NvimIO[Either[str, Path]]) 27 | def state_file(name: str) -> Do: 28 | dir = yield project_state_dir.value_or_default_e() 29 | yield dir.cata(lambda a: N.pure(Left(a)), lambda a: check_state_dir(a, name).map(Right)) 30 | 31 | 32 | @do(NvimIO[Either[str, A]]) 33 | def load_json_data_from(name: str, file: Path) -> Do: 34 | exists = yield N.from_io(IO.delay(file.exists)) 35 | if exists: 36 | json = yield N.from_io(IO.delay(file.read_text)) 37 | yield N.pure(decode_json(json)) 38 | else: 39 | yield N.pure(Left(f'state file {file} does not exist')) 40 | 41 | 42 | @do(NvimIO[Either[str, A]]) 43 | def load_json_data(name: str) -> Do: 44 | file = yield state_file(name) 45 | yield file.cata(lambda e: N.pure(Left(e)), lambda a: load_json_data_from(name, a)) 46 | 47 | 48 | @do(NS[D, None]) 49 | def load_json_state(name: str, store: UnboundLens) -> Do: 50 | state = yield NS.lift(load_json_data(name)) 51 | yield state.cata(lambda a: NS.pure(None), lambda d: NS.modify(store.set(d))) 52 | 53 | 54 | @do(NvimIO[None]) 55 | def store_json_to_file(name: str, data: A, file: Path) -> Do: 56 | json = yield N.from_either(dump_json(data)) 57 | yield N.from_io(IO.delay(file.write_text, json)) 58 | 59 | 60 | @do(NvimIO[Either[str, None]]) 61 | def store_json_data(name: str, data: A) -> Do: 62 | file = yield state_file(name) 63 | yield file.cata(lambda e: N.pure(Left(e)), lambda a: store_json_to_file(name, data, a).map(Right)) 64 | 65 | 66 | @do(NS[D, Either[str, None]]) 67 | def store_json_state(name: str, fetch: Callable[[D], A]) -> Do: 68 | payload = yield NS.inspect(fetch) 69 | yield NS.lift(store_json_data(name, payload)) 70 | 71 | 72 | __all__ = ('load_json_state', 'store_json_data', 'store_json_state') 73 | -------------------------------------------------------------------------------- /ribosome/util/string.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def escape_squote(text: str) -> str: 4 | return text.replace("'", "''") 5 | 6 | 7 | def escape_dquote(text: str) -> str: 8 | return text.replace('"', '\\"') 9 | 10 | 11 | def escape_quote(text: str) -> str: 12 | return escape_dquote(escape_squote(text)) 13 | 14 | 15 | def quote_for_ex(text: str) -> str: 16 | return f'\'{escape_quote(text)}\'' 17 | 18 | 19 | __all__ = ('escape_squote', 'escape_dquote', 'escape_quote',) 20 | -------------------------------------------------------------------------------- /ribosome/util/tmux.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | 3 | from amino.tc.base import tc_prop 4 | from amino import do, Do 5 | 6 | from chiasma.io.compute import TmuxIO 7 | from chiasma.tmux import Tmux 8 | from chiasma.io.state import TmuxIOState 9 | 10 | from ribosome.nvim.io.state import ToNvimIOState, NS 11 | from ribosome.nvim.io.compute import NvimIO 12 | from ribosome.nvim.api.variable import variable_prefixed_str 13 | from ribosome.nvim.io.api import N 14 | 15 | A = TypeVar('A') 16 | S = TypeVar('S') 17 | 18 | 19 | @do(NvimIO[Tmux]) 20 | def tmux_from_vim() -> Do: 21 | socket = yield variable_prefixed_str('tmux_socket') 22 | return Tmux.cons(socket=socket | None) 23 | 24 | 25 | @do(NvimIO[A]) 26 | def tmux_to_nvim(tm: TmuxIO[A]) -> Do: 27 | tmux = yield tmux_from_vim() 28 | yield N.from_either(tm.either(tmux)) 29 | 30 | 31 | class TmuxStateToNvimIOState(ToNvimIOState, tpe=TmuxIOState): 32 | 33 | @tc_prop 34 | def nvim(self, fa: TmuxIOState[S, A]) -> NS: 35 | return fa.transform_f(NS, tmux_to_nvim) 36 | 37 | 38 | __all__ = ('TmuxStateToNvimIOState',) 39 | -------------------------------------------------------------------------------- /scripts/doc.py: -------------------------------------------------------------------------------- 1 | #!usr/bin/env python3 2 | 3 | from typing import Callable, TypeVar 4 | 5 | from amino.logging import module_log 6 | from amino import List, Nil, IO, do, Do, Path 7 | 8 | from ribosome.util.doc.data import (StaticDoc, DocBlock, DocLine, DocString, Headline, NoMeta, DocCompiler, 9 | GeneralAnchor, Anchor) 10 | from ribosome.util.doc.markdown import markdown_compiler, MarkdownCompilerConfig 11 | from ribosome.config import settings 12 | from ribosome.util.doc.vim import vim_compiler, VimCompilerConfig 13 | from ribosome.util.doc.generate import generate_plugin_doc 14 | from ribosome.util.doc.format import CompilerConfig 15 | 16 | log = module_log() 17 | A = TypeVar('A') 18 | B = TypeVar('B') 19 | vim_conf = VimCompilerConfig.cons(CompilerConfig.cons('ribosome')) 20 | md_conf = MarkdownCompilerConfig.cons(CompilerConfig.cons('ribosome')) 21 | 22 | 23 | def report_error(output_type: str) -> Callable[[str], None]: 24 | def report_error(error: str) -> None: 25 | log.error(f'failed to generate doc as {output_type}: {error}') 26 | return report_error 27 | 28 | 29 | intro: DocBlock[None] = DocBlock(List( 30 | DocLine(DocString('Introduction', Headline.cons(1, Anchor('ribosome', GeneralAnchor())))), 31 | ), NoMeta()) 32 | pre = List(intro) 33 | post = Nil 34 | static = StaticDoc(pre, post) 35 | 36 | 37 | @do(IO[None]) 38 | def run(compiler: DocCompiler[A, B], outfile: Path) -> Do: 39 | text = yield generate_plugin_doc('ribosome.components', List(settings), static, compiler) 40 | yield IO.delay(outfile.write_text, text.join_lines) 41 | 42 | 43 | pkg_dir = Path(__file__).absolute().parent.parent 44 | run(markdown_compiler(md_conf), pkg_dir / 'README.gen.md').attempt.lmap(report_error('markdown')) 45 | # run(vim_compiler(vim_conf), pkg_dir / 'doc.gen.txt').attempt.lmap(report_error('vimdoc')) 46 | -------------------------------------------------------------------------------- /scripts/generate.py: -------------------------------------------------------------------------------- 1 | #!usr/bin/env python3 2 | 3 | from amino.meta.gen_state import state_task 4 | from amino import Path, List 5 | from amino.meta.gen import codegen_write 6 | 7 | meta_extra = '''\ 8 | def io(self, f: Callable[[NvimApi], A]) -> 'NvimIOState[S, A]': 9 | return NvimIOState.lift(N.delay(f)) 10 | 11 | def delay(self, f: Callable[[NvimApi], A]) -> 'NvimIOState[S, A]': 12 | return NvimIOState.lift(N.delay(f)) 13 | 14 | def suspend(self, f: Callable[[NvimApi], NvimIO[A]]) -> 'NvimIOState[S, A]': 15 | return NvimIOState.lift(N.suspend(f)) 16 | 17 | def from_io(self, io: IO[A]) -> 'NvimIOState[S, A]': 18 | return NvimIOState.lift(N.wrap_either(lambda v: io.attempt)) 19 | 20 | def from_id(self, st: State[S, A]) -> 'NvimIOState[S, A]': 21 | return st.transform_f(NvimIOState, lambda s: N.pure(s.value)) 22 | 23 | def from_maybe(self, a: Maybe[B], err: CallByName) -> 'NvimIOState[S, B]': 24 | return NvimIOState.lift(N.from_maybe(a, err)) 25 | 26 | m = from_maybe 27 | 28 | def from_either(self, e: Either[str, A]) -> 'NvimIOState[S, A]': 29 | return NvimIOState.lift(N.from_either(e)) 30 | 31 | e = from_either 32 | 33 | def from_either_state(self, st: EitherState[E, S, A]) -> 'NvimIOState[S, A]': 34 | return st.transform_f(NvimIOState, lambda s: N.from_either(s)) 35 | 36 | def failed(self, e: str) -> 'NvimIOState[S, A]': 37 | return NvimIOState.lift(N.failed(e)) 38 | 39 | def error(self, e: str) -> 'NvimIOState[S, A]': 40 | return NvimIOState.lift(N.error(e)) 41 | 42 | def inspect_maybe(self, f: Callable[[S], Maybe[A]], err: CallByName) -> 'NvimIOState[S, A]': 43 | return NvimIOState.inspect_f(lambda s: N.from_maybe(f(s), err)) 44 | 45 | def inspect_either(self, f: Callable[[S], Either[str, A]]) -> 'NvimIOState[S, A]': 46 | return NvimIOState.inspect_f(lambda s: N.from_either(f(s))) 47 | 48 | def simple(self, f: Callable[..., A], *a: Any, **kw: Any) -> 'NvimIOState[S, A]': 49 | return NS.lift(N.simple(f, *a, **kw)) 50 | 51 | def sleep(self, duration: float) -> 'NvimIOState[S, None]': 52 | return NS.lift(N.sleep(duration)) 53 | ''' 54 | 55 | extra = ''' 56 | NS = NvimIOState 57 | 58 | 59 | class ToNvimIOState(TypeClass): 60 | 61 | @abc.abstractproperty 62 | def nvim(self) -> NS: 63 | ... 64 | 65 | 66 | class IdStateToNvimIOState(ToNvimIOState, tpe=State): 67 | 68 | @tc_prop 69 | def nvim(self, fa: State[S, A]) -> NS: 70 | return NvimIOState.from_id(fa) 71 | 72 | 73 | class EitherStateToNvimIOState(ToNvimIOState, tpe=EitherState): 74 | 75 | @tc_prop 76 | def nvim(self, fa: EitherState[E, S, A]) -> NS: 77 | return NvimIOState.from_either_state(fa) 78 | ''' 79 | 80 | extra_import = List( 81 | 'import abc', 82 | 'from amino.tc.base import TypeClass, tc_prop', 83 | 'from amino.state import State, EitherState', 84 | 'from ribosome.nvim.api.data import NvimApi', 85 | 'from ribosome.nvim.io.api import N', 86 | 'from amino import IO, Maybe, Either', 87 | 'from amino.func import CallByName', 88 | '''E = TypeVar('E')''', 89 | ) 90 | pkg = Path(__file__).absolute().parent.parent 91 | task = state_task('NvimIO', 'ribosome.nvim.io.compute', meta_extra=meta_extra, ctor_extra=meta_extra, 92 | extra_import=extra_import, extra=extra) 93 | outpath = pkg / 'ribosome' / 'nvim' / 'io' / f'state.py' 94 | 95 | codegen_write(task, outpath).fatal 96 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | version_parts = (13, 0, 2, 'a', 8) 4 | version = '.'.join(map(str, version_parts)) 5 | 6 | setup( 7 | name='ribosome', 8 | description='neovim plugin framework', 9 | version=version, 10 | author='Torsten Schmits', 11 | author_email='torstenschmits@gmail.com', 12 | license='MIT', 13 | url='https://github.com/tek/ribosome', 14 | packages=find_packages(exclude=['unit', 'unit.*', 'integration', 'integration.*', 'test', 'test.*']), 15 | install_requires=[ 16 | 'amino~=13.0.1a9', 17 | 'msgpack-python~=0.5.6' 18 | ], 19 | tests_require=[ 20 | 'chiasma~=0.1.0.a28', 21 | 'kallikrein~=0.22.0a15', 22 | ], 23 | entry_points={ 24 | 'console_scripts': [ 25 | 'ribosome_start_plugin = ribosome.cli:start_plugin', 26 | ], 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /test/settings.py: -------------------------------------------------------------------------------- 1 | from amino import List, do, Do 2 | from amino.logging import module_log 3 | 4 | from ribosome.compute.api import prog 5 | from ribosome.rpc.api import rpc 6 | from ribosome.config.config import Config, NoData 7 | from ribosome.nvim.io.state import NS 8 | from ribosome.config.component import NoComponentData 9 | from ribosome.compute.ribosome import Ribosome 10 | from ribosome.compute.ribosome_api import Ribo 11 | from ribosome.config.setting import int_setting 12 | 13 | log = module_log() 14 | counter = int_setting('counter', 'counter', '', False) 15 | inc = int_setting('inc', 'inc', '', False) 16 | 17 | 18 | @prog.unit 19 | @do(NS[Ribosome[NoData, NoComponentData, NoData], None]) 20 | def check() -> Do: 21 | ctr = yield Ribo.setting(counter) 22 | i = yield Ribo.setting(inc) 23 | yield NS.lift(counter.update(ctr + i)) 24 | 25 | 26 | settings_spec_config: Config = Config.cons( 27 | name='plug', 28 | rpc=List( 29 | rpc.write(check), 30 | ), 31 | internal_component=False, 32 | ) 33 | 34 | __all__ = ('settings_spec_config',) 35 | -------------------------------------------------------------------------------- /unit/__init__.py: -------------------------------------------------------------------------------- 1 | import amino.test 2 | amino.test.setup(__file__) 3 | -------------------------------------------------------------------------------- /unit/_support/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tek/ribosome-py/8bd22e549ddff1ee893d6e3a0bfba123a09e96c6/unit/_support/__init__.py -------------------------------------------------------------------------------- /unit/_support/spec.py: -------------------------------------------------------------------------------- 1 | import amino 2 | import amino.test 3 | from amino.logging import amino_stdout_logging 4 | from amino.test.spec_spec import Spec as SpecBase 5 | 6 | 7 | class Spec(SpecBase): 8 | 9 | def setup(self): 10 | amino.development = True 11 | amino_stdout_logging() 12 | super().setup() 13 | 14 | 15 | __all__ = ('Spec',) 16 | -------------------------------------------------------------------------------- /unit/doc_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import Expectation 2 | from kallikrein.expectable import kio 3 | from kallikrein.matchers.lines import have_lines 4 | 5 | from amino.test.spec import SpecBase 6 | from amino import List, Nil 7 | 8 | from ribosome.config.settings import ribosome_session_name 9 | from ribosome.util.doc.data import StaticDoc 10 | from ribosome.util.doc.markdown import markdown_compiler, MarkdownCompilerConfig 11 | from ribosome.util.doc.generate import generate_doc 12 | from ribosome.util.doc.format import CompilerConfig 13 | 14 | 15 | target = '''# Components 16 | 17 | # Settings 18 | 19 | ## `g:ribosome_session_name` 20 | 21 | project name from user var 22 | 23 | A custom session name for the state dir can be specified. 24 | 25 | ''' 26 | conf = MarkdownCompilerConfig.cons(CompilerConfig.cons('ribosome')) 27 | 28 | 29 | class DocSpec(SpecBase): 30 | ''' 31 | markdown compiler $markdown 32 | ''' 33 | 34 | def markdown(self) -> Expectation: 35 | return kio(generate_doc, Nil, List(ribosome_session_name), StaticDoc.cons(), markdown_compiler(conf)).must( 36 | have_lines(target)) 37 | 38 | 39 | __all__ = ('DocSpec',) 40 | -------------------------------------------------------------------------------- /unit/klk_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import k, Expectation 2 | 3 | from ribosome.nvim.io.data import NSuccess, NFatal 4 | 5 | from amino.test.spec import SpecBase 6 | from ribosome.test.klk.matchers.nresult import nsuccess, nfatal 7 | 8 | 9 | class KlkSpec(SpecBase): 10 | ''' 11 | success $success 12 | fatal $fatal 13 | ''' 14 | 15 | def success(self) -> Expectation: 16 | return k(NSuccess(5)).must(nsuccess(5)) 17 | 18 | def fatal(self) -> Expectation: 19 | x = Exception('boom') 20 | return k(NFatal(x)).must(nfatal(x)) 21 | 22 | 23 | __all__ = ('KlkSpec',) 24 | -------------------------------------------------------------------------------- /unit/logger_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import Expectation, k 2 | from kallikrein.matchers import contain 3 | 4 | from amino import Dat, List, Nil, __, do, Do 5 | from amino.test.spec import SpecBase 6 | 7 | from ribosome.config.config import Config 8 | from ribosome.compute.output import Echo 9 | from ribosome.nvim.io.state import NS 10 | from ribosome.compute.api import prog 11 | from ribosome.rpc.api import rpc 12 | from ribosome.test.config import TestConfig 13 | from ribosome.test.prog import request 14 | from ribosome.test.unit import unit_test 15 | 16 | 17 | class LSData(Dat['LSData']): 18 | 19 | def __init__(self, log: List[Echo]=Nil) -> None: 20 | self.log = log 21 | 22 | 23 | test_echo = Echo.info('text') 24 | 25 | 26 | @prog.echo 27 | def log_something() -> NS[LSData, None]: 28 | return NS.pure(test_echo) 29 | 30 | 31 | @prog 32 | def logger(msg: Echo) -> NS[LSData, None]: 33 | return NS.modify(__.append1.log(msg)) 34 | 35 | 36 | config: Config = Config.cons( 37 | 'logger', 38 | state_ctor=LSData, 39 | rpc=List( 40 | rpc.write(log_something), 41 | ), 42 | ) 43 | test_config = TestConfig.cons(config, logger=logger) 44 | 45 | 46 | @do(NS[LSData, Expectation]) 47 | def logger_spec() -> Do: 48 | yield request('log_something') 49 | log = yield NS.inspect(lambda s: s.data.log) 50 | return k(log).must(contain(test_echo)) 51 | 52 | 53 | class LoggerSpec(SpecBase): 54 | ''' 55 | use a custom logger $logger 56 | ''' 57 | 58 | def logger(self) -> Expectation: 59 | return unit_test(test_config, logger_spec) 60 | 61 | 62 | __all__ = ('LoggerSpec',) 63 | -------------------------------------------------------------------------------- /unit/mapping_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import Expectation, k, pending 2 | from kallikrein.matchers import contain 3 | from kallikrein.matchers.start_with import start_with 4 | from kallikrein.matchers.maybe import be_just 5 | 6 | from amino import do, Do, __, Map, List, _, Dat 7 | from amino.boolean import true 8 | from amino.lenses.lens import lens 9 | from amino.test.spec import SpecBase 10 | 11 | from ribosome.compute.api import prog 12 | from ribosome.nvim.io.state import NS 13 | from ribosome.nvim.io.compute import NvimIO 14 | from ribosome.test.config import default_config_name, TestConfig 15 | from ribosome.rpc.api import rpc 16 | from ribosome.data.plugin_state import PluginState, PS 17 | from ribosome.config.component import Component, ComponentData 18 | from ribosome.config.config import Config, NoData 19 | from ribosome.nvim.api.command import nvim_command_output 20 | from ribosome.data.mapping import Mappings, Mapping, mapmode 21 | from ribosome.components.internal.mapping import activate_mapping 22 | from ribosome.test.prog import request 23 | from ribosome.test.unit import unit_test 24 | from ribosome.test.integration.external import external_state_test 25 | 26 | keys = 'gs' 27 | gs_mapping = Mapping.cons('test-mapping', 'gs', true, List(mapmode.Normal(), mapmode.Visual())) 28 | 29 | 30 | class CData(Dat['CData']): 31 | 32 | @staticmethod 33 | def cons(a: int=13) -> 'CData': 34 | return CData(a) 35 | 36 | def __init__(self, a: int) -> None: 37 | self.a = a 38 | 39 | 40 | @prog.unit 41 | @do(NS[ComponentData[NoData, CData], None]) 42 | def handle_map() -> Do: 43 | yield NS.modify(lens.comp.a.set(27)) 44 | 45 | 46 | @prog.unit 47 | @do(NS[PluginState[NoData, None], None]) 48 | def setup_map() -> Do: 49 | yield activate_mapping(gs_mapping) 50 | yield NS.unit 51 | 52 | 53 | component: Component = Component.cons( 54 | 'main', 55 | state_type=CData, 56 | rpc=List( 57 | rpc.write(setup_map), 58 | rpc.write(handle_map).conf(json=true) 59 | ), 60 | mappings=Mappings.cons( 61 | (gs_mapping, handle_map), 62 | ) 63 | ) 64 | config: Config = Config.cons( 65 | name=default_config_name, 66 | prefix=default_config_name, 67 | components=Map(main=component), 68 | ) 69 | test_config = TestConfig.cons(config, components=List('main')) 70 | 71 | 72 | @do(NS[PS, Expectation]) 73 | def buffer_spec() -> Do: 74 | yield request('setup_map') 75 | yield request('map', gs_mapping.ident, 'gs') 76 | maps = yield NS.lift(nvim_command_output('map ')) 77 | data = yield NS.inspect(lambda s: s.data_by_type(CData)) 78 | return ( 79 | k(maps).must(contain(start_with(f'x {keys}')) & contain(start_with(f'n {keys}'))) & 80 | (k(data.a) == 27) 81 | ) 82 | 83 | 84 | # FIXME 85 | class MappingSpec(SpecBase): 86 | ''' 87 | map a key buffer-local $buffer 88 | ''' 89 | 90 | def buffer(self) -> Expectation: 91 | return external_state_test(test_config, buffer_spec) 92 | 93 | 94 | __all__ = ('MappingSpec',) 95 | -------------------------------------------------------------------------------- /unit/menu_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import Expectation, k 2 | from kallikrein.matchers.length import have_length 3 | 4 | from amino.test.spec import SpecBase 5 | from amino import Map, List, do, Do, Dat, Nil 6 | from amino.logging import module_log 7 | 8 | from ribosome.test.integration.external import external_state_test 9 | from ribosome.config.config import Config 10 | from ribosome.test.config import default_config_name, TestConfig 11 | from ribosome.config.component import Component 12 | from ribosome.rpc.api import rpc 13 | from ribosome.compute.api import prog 14 | from ribosome.nvim.io.state import NS 15 | from ribosome.data.plugin_state import PS 16 | from ribosome.test.prog import fork_request 17 | from ribosome.util.menu.data import MenuAction, MenuContent, MenuState, MenuLine, MenuUnit, MenuConfig 18 | from ribosome.util.menu.run import run_menu_prog 19 | from ribosome.nvim.api.ui import send_input, current_cursor 20 | from ribosome.nvim.api.function import define_function 21 | from ribosome.nvim.api.variable import var_becomes 22 | from ribosome.util.menu.prompt.data import InputState, PromptUpdate 23 | from ribosome.test.klk.matchers.buffer import current_buffer_matches 24 | from ribosome.util.menu.auto.run import auto_menu 25 | from ribosome.util.menu.auto.data import AutoUpdate 26 | from ribosome.test.klk.matchers.window import current_cursor_is 27 | 28 | log = module_log() 29 | 30 | 31 | class MState(Dat['MState']): 32 | 33 | def __init__(self) -> None: 34 | pass 35 | 36 | 37 | lines = List( 38 | MenuLine.cons('first', None), 39 | MenuLine.cons('second', None), 40 | MenuLine.cons('third', None), 41 | ) 42 | menu_state = MState() 43 | menu = auto_menu(menu_state, lines, MenuConfig.cons('spec menu', False)) 44 | 45 | 46 | @prog.do(None) 47 | def spec_menu() -> Do: 48 | yield run_menu_prog(menu) 49 | 50 | 51 | component: Component = Component.cons( 52 | 'main', 53 | rpc=List( 54 | rpc.write(spec_menu), 55 | ), 56 | ) 57 | config: Config = Config.cons( 58 | name=default_config_name, 59 | prefix=default_config_name, 60 | components=Map(main=component), 61 | ) 62 | test_config = TestConfig.cons(config, components=List('main')) 63 | 64 | 65 | loop_fun = '''let g:looping = 1 66 | while g:looping 67 | sleep 100m 68 | endwhile 69 | ''' 70 | 71 | 72 | @do(NS[PS, Expectation]) 73 | def menu_spec() -> Do: 74 | yield NS.lift(define_function('Loop', Nil, loop_fun)) 75 | yield NS.lift(send_input(':call Loop()')) 76 | yield fork_request('spec_menu') 77 | yield NS.lift(var_becomes('looping', 1)) 78 | yield NS.lift(send_input('ir')) 79 | yield NS.lift(send_input('')) 80 | yield NS.lift(send_input('')) 81 | yield NS.lift(send_input('')) 82 | content = yield NS.lift(current_buffer_matches(have_length(2))) 83 | line, col = yield NS.lift(current_cursor()) 84 | cursor = yield NS.lift(current_cursor_is(1, 0)) 85 | return content & cursor 86 | 87 | 88 | class MenuSpec(SpecBase): 89 | ''' 90 | run a menu $menu 91 | ''' 92 | 93 | def menu(self) -> Expectation: 94 | return external_state_test(test_config, menu_spec) 95 | 96 | 97 | __all__ = ('MenuSpec',) 98 | -------------------------------------------------------------------------------- /unit/persist_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import k, Expectation 2 | 3 | from amino.test.spec import SpecBase 4 | from amino.test import temp_dir 5 | from amino import Map, Dat, do, Do 6 | from amino.lenses.lens import lens 7 | 8 | from ribosome.nvim.api.data import StrictNvimApi 9 | from ribosome.nvim.io.compute import NvimIO 10 | from ribosome.util.persist import store_json_state, load_json_state 11 | from ribosome.test.klk.matchers.nresult import nsuccess 12 | 13 | state_dir = temp_dir('state') 14 | vars = Map( 15 | ribosome_state_dir=str(state_dir), 16 | proteome_main_name='spec', 17 | ) 18 | vim = StrictNvimApi.cons('persist', vars=vars) 19 | 20 | 21 | class Counters(Dat['Counters']): 22 | 23 | def __init__(self, a: int, b: int) -> None: 24 | self.a = a 25 | self.b = b 26 | 27 | 28 | class Data(Dat['Data']): 29 | 30 | def __init__(self, counters: Counters) -> None: 31 | self.counters = counters 32 | 33 | 34 | data = Data(Counters(5, 9)) 35 | 36 | 37 | @do(NvimIO[Expectation]) 38 | def persist_spec() -> Do: 39 | data1 = Data(Counters(0, -17)) 40 | yield store_json_state('counters', lambda a: a.counters).run(data) 41 | yield load_json_state('counters', lens.counters).run_s(data1) 42 | 43 | 44 | class PersistSpec(SpecBase): 45 | ''' 46 | store and load data as json to a file $persist 47 | ''' 48 | 49 | def persist(self) -> Expectation: 50 | return k(persist_spec().run_a(vim)).must(nsuccess(data)) 51 | 52 | 53 | __all__ = ('PersistSpec',) 54 | -------------------------------------------------------------------------------- /unit/request/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = () 4 | -------------------------------------------------------------------------------- /unit/rpc_spec.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, TypeVar, Tuple 2 | import asyncio 3 | 4 | from kallikrein import Expectation, pending, k 5 | from kallikrein.matchers import contain 6 | from kallikrein.matchers.either import be_right 7 | from kallikrein.expectable import kio 8 | 9 | from amino import List, do, Do, Map, IO, Path, Left 10 | from amino.test import temp_file 11 | from amino.test.spec import SpecBase 12 | from amino.logging import module_log 13 | 14 | from ribosome.nvim.io.compute import NvimIO, NRParams 15 | from ribosome.nvim.api.variable import variable_set, variable_raw 16 | from ribosome.nvim.api.command import nvim_command 17 | from ribosome.nvim.api.rpc import channel_id 18 | from ribosome.nvim.api.function import nvim_call_function 19 | from ribosome.nvim.io.api import N 20 | from ribosome.rpc.comm import Comm, RpcComm, StateGuard, exclusive_ns 21 | from ribosome.config.config import Config 22 | from ribosome.rpc.start import start_comm, stop_comm, plugin_execute_receive_request 23 | from ribosome.compute.api import prog 24 | from ribosome.nvim.io.state import NS 25 | from ribosome.rpc.api import rpc 26 | from ribosome.rpc.state import cons_state 27 | from ribosome.components.internal.update import init_rpc 28 | from ribosome.rpc.to_plugin import rpc_handler 29 | from ribosome.rpc.nvim_api import RiboNvimApi 30 | from ribosome.rpc.native.io import Asyncio, cons_asyncio_embed 31 | 32 | log = module_log() 33 | A = TypeVar('A') 34 | 35 | 36 | def stop() -> None: 37 | asyncio.get_event_loop().stop() 38 | 39 | 40 | def main_loop() -> None: 41 | try: 42 | asyncio.run_event_loop() 43 | except Exception as e: 44 | log.error(e) 45 | 46 | 47 | value = 'successfully set variable' 48 | 49 | 50 | @do(NvimIO[None]) 51 | def run2() -> Do: 52 | channel = yield channel_id() 53 | yield variable_set('foo', value) 54 | v = yield variable_raw('foo') 55 | yield N.recover_failure( 56 | nvim_call_function('rpcrequest', channel, 'ping', params=NRParams.cons(sync=True)), 57 | lambda a: N.pure('failed') 58 | ) 59 | return v 60 | 61 | 62 | @do(NvimIO[None]) 63 | def run1() -> Do: 64 | v = yield run2() 65 | yield nvim_command('quit') 66 | return v 67 | 68 | 69 | responses: Map[str, Any] = Map( 70 | nvim_get_api_info=(1, {}), 71 | ) 72 | 73 | 74 | @prog 75 | @do(NS[None, None]) 76 | def ping() -> Do: 77 | yield NS.unit 78 | 79 | 80 | config: Config = Config.cons('uv', rpc=List(rpc.write(ping))) 81 | 82 | 83 | def embed_nvim(log: Path) -> Tuple[Asyncio, RpcComm]: 84 | embed_nvim_cmdline = List('nvim', f'-V{log}', '-n', '-u', 'NONE', '--embed') 85 | return cons_asyncio_embed(embed_nvim_cmdline) 86 | 87 | 88 | @do(IO[A]) 89 | def run_nvim(comm: Comm, config: Config, io: Callable[[], NvimIO[A]]) -> Do: 90 | state = cons_state(config) 91 | guard = StateGuard.cons(state) 92 | execute_request = plugin_execute_receive_request(guard, 'spec') 93 | yield start_comm(comm, execute_request) 94 | api = RiboNvimApi(config.basic.name, comm) 95 | yield N.to_io_a(exclusive_ns(guard, 'init_rpc', init_rpc, Left('')), api) 96 | s, r = io().run(api) 97 | yield stop_comm(comm) 98 | return r 99 | 100 | 101 | class RpcSpec(SpecBase): 102 | ''' 103 | native session $native 104 | ''' 105 | 106 | def native(self) -> Expectation: 107 | log = temp_file('log', 'uv') 108 | uv, rpc_comm = embed_nvim(log) 109 | comm = Comm.cons(rpc_handler, rpc_comm) 110 | return kio(run_nvim, comm, config, run1).must(contain(be_right(value))) 111 | 112 | 113 | __all__ = ('RpcSpec',) 114 | -------------------------------------------------------------------------------- /unit/settings_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import Expectation, k 2 | 3 | from ribosome.nvim.api.variable import variable_set, variable_num 4 | 5 | from amino import do, Do 6 | from amino.test.spec import SpecBase 7 | from ribosome.nvim.io.compute import NvimIO 8 | from ribosome.test.integration.external import external_state_test 9 | from ribosome.test.integration.embed import TestConfig 10 | from ribosome.nvim.io.state import NS 11 | from ribosome.test.prog import request 12 | 13 | from test.settings import settings_spec_config 14 | 15 | 16 | @do(NS[None, None]) 17 | def update_setting_spec() -> Do: 18 | yield request('check') 19 | n = yield NS.lift(variable_num('counter')) 20 | return k(n) == 21 21 | 22 | 23 | @do(NvimIO[None]) 24 | def pre() -> Do: 25 | yield variable_set('counter', 7) 26 | yield variable_set('inc', 14) 27 | 28 | 29 | test_config = TestConfig.cons(settings_spec_config, pre=pre) 30 | 31 | 32 | class SettingsSpec(SpecBase): 33 | ''' 34 | update a setting $update 35 | ''' 36 | 37 | def update(self) -> Expectation: 38 | return external_state_test(test_config, update_setting_spec) 39 | 40 | 41 | __all__ = ('SettingsSpec',) 42 | -------------------------------------------------------------------------------- /unit/update_state_spec.py: -------------------------------------------------------------------------------- 1 | from kallikrein import k, Expectation, pending 2 | from kallikrein.matchers.maybe import be_just 3 | 4 | from ribosome.config.component import Component 5 | from ribosome.config.config import Config 6 | 7 | from amino.test.spec import SpecBase 8 | from amino.dat import Dat 9 | from amino.json import dump_json 10 | from amino import List, Map, _, __ 11 | 12 | 13 | class Item(Dat['Item']): 14 | 15 | def __init__(self, name: str, value: int) -> None: 16 | self.name = name 17 | self.value = value 18 | 19 | 20 | class D2(Dat['D2']): 21 | 22 | def __init__(self, a: str, items: List[Item]) -> None: 23 | self.a = a 24 | self.items = items 25 | 26 | 27 | class D1(Dat['D1']): 28 | 29 | def __init__(self, d: D2) -> None: 30 | self.d = d 31 | 32 | 33 | items = List(Item('first', 4), Item('second', 7)) 34 | 35 | 36 | class USData(Dat['USData']): 37 | 38 | @staticmethod 39 | def cons() -> 'USData': 40 | return USData(D1(D2('value', items))) 41 | 42 | def __init__(self, d: D1) -> None: 43 | self.d = d 44 | 45 | 46 | class C1Data(Dat['C1Data']): 47 | 48 | @staticmethod 49 | def cons() -> 'C1Data': 50 | return C1Data(D1(D2('value', items))) 51 | 52 | def __init__(self, d: D1) -> None: 53 | self.d = d 54 | 55 | 56 | c1: Component = Component.cons('c1', state_type=C1Data) 57 | config: Config = Config.cons('us', state_ctor=USData.cons, components=Map(c1=c1)) 58 | 59 | 60 | class UpdateStateSpec(SpecBase): 61 | ''' 62 | update scalar $scalar 63 | update list $list 64 | update component state $component 65 | ''' 66 | 67 | @pending 68 | def scalar(self) -> Expectation: 69 | helper = RequestHelper.strict(config) 70 | new = 'new' 71 | data = dump_json(dict(patch=dict(query='d.d', data=dict(a=new)))).get_or_raise() 72 | r = helper.unsafe_run_s('update_state', args=data.split(' ')) 73 | return k(r.data.d) == D1(D2(new, items)) 74 | 75 | @pending 76 | def list(self) -> Expectation: 77 | helper = RequestHelper.strict(config) 78 | new = 21 79 | data = dump_json(dict(patch=dict(query='d.d.items(name=second)', data=dict(value=new)))).get_or_raise() 80 | r = helper.unsafe_run_s('update_state', args=data.split(' ')) 81 | return k(r.data.d) == D1(D2('value', List(Item('first', 4), Item('second', new)))) 82 | 83 | @pending 84 | def component(self) -> Expectation: 85 | helper = RequestHelper.strict(config, 'c1').mod.state(__.update_component_data(C1Data.cons())) 86 | new = 'new' 87 | data = dump_json(dict(patch=dict(query='d.d', data=dict(a=new)))).get_or_raise() 88 | r = helper.unsafe_run_s('update_component_state', args=['c1'] + data.split(' ')) 89 | return k(r.component_data.lift(C1Data) / _.d).must(be_just(D1(D2(new, items)))) 90 | 91 | 92 | __all__ = ('UpdateStateSpec',) 93 | --------------------------------------------------------------------------------