├── .gitignore ├── CHANGELOG ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Makefile ├── README.md ├── Tidal.ghci ├── bin ├── generate-completions ├── tidal ├── tidal.bat └── tidalvim ├── boot.sc ├── ftdetect └── tidal.vim ├── ftplugin └── tidal.vim ├── plugin └── tidal.vim └── syntax └── tidal.vim /.gitignore: -------------------------------------------------------------------------------- 1 | .dirt-samples 2 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 0.3.3 2 | 3 | * Fix syntax highlighting issues with the `#` operator 4 | 5 | 0.3.2 6 | 7 | * Update README 8 | 9 | 0.3.1 10 | 11 | * Add mapping for sending paragraphs, on Normal, Visual and Insert modes 12 | * Flash text selection when sending text 13 | * Force tmux to use 256 colors on tidalvim 14 | * Add maping as an alternative to h 15 | * Try to use getcurpos() if available when restoring cursor position 16 | 17 | 0.3.0 18 | 19 | * Update bootstrap script for Tidal 0.9 20 | * `tidalvim` now only creates 2 panes for Vim and GHCi 21 | * Split `tidalvim` script in two scripts: `tidal` and `tidalvim` 22 | * Better instructions for customizing Tidal bootstrap script 23 | 24 | 0.2.0 25 | 26 | * Remove vim-slime dependency 27 | * Preserve cursor position when sending to tmux 28 | * Configure tmux automatically with defaults 29 | 30 | 0.1.0 31 | 32 | * Use Slime to communicate via tmux 33 | * Create "tidalvim" script for creating Dirt and Tidal instances 34 | * Define more vim-like default bindings 35 | * Add bindings to silence channels 36 | * Add bindings for playing first occurrence of stream 37 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at munshkr@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Damián Emiliano Silvani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | mkfile_path := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) 2 | 3 | prefix=/usr/local 4 | 5 | install: 6 | ln -fs $(mkfile_path)/bin/tidal $(prefix)/bin 7 | ln -fs $(mkfile_path)/bin/tidalvim $(prefix)/bin 8 | 9 | uninstall: 10 | rm -f $(prefix)/bin/tidal $(prefix)/bin/tidalvim 11 | 12 | .PHONY: install uninstall 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-tidal # 2 | 3 | A Vim/NeoVim plugin for [TidalCycles](http://tidalcycles.org), the language for 4 | live coding musical patterns written in Haskell. 5 | 6 | This plugin by default uses [tmux](https://tmux.github.io/), a known and loved 7 | terminal multiplexer, for communicating with between Vim and the Tidal 8 | interpreter. It was originally based on 9 | [vim-slime](https://github.com/jpalardy/vim-slime). 10 | 11 | ![](http://i.imgur.com/frOLFFI.gif) 12 | 13 | If you are using Vim8 or NeoVim, you can use the native Terminal feature instead 14 | of tmux. Read the Configuration section on how to enable it. 15 | 16 | [![asciicast](https://asciinema.org/a/224891.svg)](https://asciinema.org/a/224891) 17 | 18 | ## Getting Started ## 19 | 20 | 1. Start livecoding with Vim by simply running: 21 | 22 | ```bash 23 | $ tidalvim 24 | ``` 25 | 26 | This creates a tmux session with Vim and Tidal running on different panes. 27 | 28 | 2. Write something like this: 29 | 30 | ```haskell 31 | d1 $ sound "bd sn" 32 | ``` 33 | 34 | 3. While being on that line, press `` (Control + E) to evaluate it. 35 | 36 | You should see Vim flash that line for a second and a chunk of text appear on 37 | your Tidal interpreter. If you already have SuperDirt or other synth running, 38 | you should hear a kick and a snare :) 39 | 40 | ## Install ## 41 | 42 | Make sure you have TidalCycles installed, with SuperDirt running. See [the Tidal wiki](https://tidalcycles.org/index.php/Userbase) for more information. 43 | 44 | ### Install tmux ### 45 | 46 | #### Ubuntu/Debian #### 47 | 48 | You can install it from the main repos: 49 | 50 | $ sudo apt-get install tmux 51 | 52 | #### OSX #### 53 | 54 | $ brew install tmux 55 | 56 | #### Windows #### 57 | 58 | There seems to be [a Cygwin package for 59 | tmux](https://cygwin.com/cgi-bin2/package-cat.cgi?file=x86%2Ftmux%2Ftmux-1.9a-1&grep=tmux), 60 | but at present it is [not working](https://github.com/microsoft/terminal/issues/5132#issuecomment-604560893) with any known terminal emulator for Windows. As such, this plugin has only been tested with the *Windows native* build of [Neovim](https://github.com/tidalcycles/vim-tidal#neovim-terminal-target). 61 | 62 | 63 | ### Install plugin ### 64 | 65 | I recommend using a Vim plugin manager like 66 | [Plug](https://github.com/junegunn/vim-plug). Check the link for instructions 67 | on installing and configuring. If you don't want a plugin manager, you can 68 | also download the latest release 69 | [here](https://github.com/tidalcycles/vim-tidal/releases) and extract the 70 | contents on your Vim directory (usually `~/.vim/`). 71 | 72 | For example, with Plug you need to: 73 | 74 | * Edit your `.vimrc` file and add these lines: 75 | 76 | ```vim 77 | Plug 'tidalcycles/vim-tidal' 78 | ``` 79 | 80 | * Restart Vim and execute `:PlugInstall` to automatically download and 81 | install the plugins. 82 | 83 | #### UNIX-based Systems #### 84 | 85 | If you are on a UNIX-based operating system (Linux distributions, MacOS, etc.), go to the plugin repository and run `make install`: 86 | 87 | (if you are using NeoVim and you won't run tmux then you don't need to run `make install` to be able to load the plugin inside NeoVim) 88 | 89 | $ cd ~/.vim/plugged/vim-tidal 90 | $ sudo make install 91 | 92 | This creates symlinks on `/usr/local/bin` for `tidal` and `tidalvim` scripts. 93 | You can remove them later if you want with `make uninstall`. 94 | 95 | #### Windows #### 96 | 97 | :warning: **This plugin has only been tested on Windows 10 using Neovim >0.5** 98 | 99 | If you are on Windows, add the `vim-tidal\bin` directory to your `PATH` user environment variable: 100 | 101 | 1. Click the `Start` button 102 | 2. Type "Edit the system environment variables" and hit `enter` or click on the search result 103 | 3. Click the button labeled `Environment variables...` 104 | 4. In the `User variables for [username]` table, click the entry for the `Path` variable, followed by the `Edit...` button beneath the same table 105 | 5. Click the `New` button in the following dialog, enter the *full path* to the `vim-tidal\bin` directory, and click `OK` until all the preceding dialogs are closed. 106 | 107 | Note: The full path to the `vim-tidal\bin` directory, will look something like `C:\Users\[username]\AppData\Local\nvim\plugged\vim-tidal\bin`, assuming you are using vim-plug as this document recommends. 108 | 109 | #### Final Installation Note #### 110 | 111 | Make sure to have the `filetype plugin on` setting on your .vimrc, otherwise 112 | plugin won't be loaded when opening a .tidal file. 113 | 114 | ### Older Tidal versions (pre 1.0) ### 115 | 116 | Tidal 1.0 introduces some breaking changes, so if haven't upgraded yet, you can 117 | still use this plugin with an older version. Just point your Plug entry to use 118 | the `tidal-0.9` branch. 119 | 120 | First change your Plug line on your `.vimrc` to: 121 | 122 | ```vim 123 | Plug 'tidalcycles/vim-tidal', {'branch': 'tidal-0.9'} 124 | ``` 125 | 126 | Then on Vim run `:PlugInstall` to update your plugin. 127 | 128 | 129 | ## Usage 130 | 131 | This plugin comes bundled with two Bash scripts: `tidalvim` and `tidal`. 132 | 133 | ### `tidalvim` 134 | 135 | `tidalvim` starts a tmux session with the screen horizontally splitted, having 136 | Vim on the upper pane and the Tidal interpreter on the lower pane. This is the 137 | simplest way to start using Tidal with Vim. 138 | 139 | You don't have to use `tidalvim` necessarily. If you have a more complex setup 140 | or just want to use Vim outside of tmux, you can use `tidal`. See below. 141 | 142 | ### `tidal` 143 | 144 | `tidal` fires up GHCi (the Glasgow Haskell interpreter) and runs a bootstrap 145 | file that loads Tidal up. `tidalvim` uses this script to start the Tidal 146 | interpreter on the lower pane. You can even use it standalone (without Vim) by 147 | simply running `tidal` from your shell. 148 | 149 | ```haskell 150 | $ tidal 151 | GHCi, version 7.10.3: http://www.haskell.org/ghc/ :? for help 152 | tidal> d1 $ sound "bd sn" 153 | tidal> :t density 2 $ n "0 1" 154 | density 2 $ n "0 1" :: Pattern ParamMap 155 | ``` 156 | 157 | So, in case you don't want to use `tidalvim`, just run the following on another 158 | terminal: 159 | 160 | ```bash 161 | tmux new-session -s tidal tidal 162 | ``` 163 | 164 | What `tidal` does is actually run `ghci` with the argument `-ghci-script 165 | Tidal.ghci`. [Tidal.ghci](Tidal.ghci) is found at the root of the repository, 166 | and is responsible for bootstraping Tidal. See Configure section for more on 167 | how to customize Tidal bootstraping process. Any extra arguments when running 168 | `tidal` will be delegated to `ghci`. 169 | 170 | ### Commands 171 | 172 | These are some of the commands that can be run from Vim command line: 173 | 174 | * `:TidalSend`: Send a `[range]` of lines. If no range is provided the 175 | current line is sent. 176 | 177 | * `:TidalSend1 {text}`: Send a single line of text specified on the command 178 | line. 179 | 180 | * `:TidalConfig`: Configure tmux socket name and target pane 181 | 182 | * `:TidalSilence [num]`: Silence stream number `[num]` by sending `d[num] 183 | silence`. 184 | 185 | * `:TidalPlay [num]`: Send first ocurrence of stream number `[num`] from the 186 | current cursor position. 187 | 188 | * `:TidalHush`: Silences all streams by sending `hush`. 189 | 190 | * `:TidalGenerateCompletions {path}`: Generate dictionary for Dirt-Samples 191 | completion (path is optional). 192 | 193 | ### Default bindings 194 | 195 | Using one of these key bindings you can send lines to Tidal: 196 | 197 | * `` (Control+E), `ss`: Send current inner paragraph. 198 | * `s`: Send current line or current visually selected block. 199 | 200 | `` can be called on either Normal, Visual, Select or Insert mode, so it is 201 | probably easier to type than `ss` or `s`. 202 | 203 | There are other bindings to control Tidal like: 204 | 205 | * `s[num]`: Call `:TidalPlay [num]` 206 | * `[num]`: Call `:TidalSilence [num]` 207 | * `h`, ``: Call `:TidalHush` 208 | 209 | #### About `` 210 | 211 | The `` key is a special key used to perform commands with a sequence of 212 | keys. The `` key behaves as the `` key, but is *local* to 213 | a buffer. In particular, the above bindings only work in buffers with the 214 | "tidal" file type set, e.g. files whose file type is `.tidal` 215 | 216 | By default, there is no `` set. To define one, e.g. for use with 217 | a comma (`,`), write this on your `.vimrc` file: 218 | 219 | ```vim 220 | let maplocalleader="," 221 | ``` 222 | 223 | Reload your configuration (or restart Vim), and after typing `,ss` on a few 224 | lines of code, you should see those being copied onto the Tidal interpreter on 225 | the lower pane. 226 | 227 | 228 | ## Configure ## 229 | 230 | ### GHCI 231 | 232 | By default, `vim-tidal` uses the globally installed GHCI to launch the REPL. 233 | If you have installed Tidal through Stack (`stack install tidal`) or some other 234 | means, you can specify another command to use with `g:tidal_ghci`. 235 | 236 | For example, if one installed Tidal with Stack, they would use: 237 | 238 | ```vim 239 | let g:tidal_ghci = "stack exec ghci --" 240 | ``` 241 | 242 | ### Tidal Boot File 243 | 244 | A "Tidal boot file" is a file that may be used to initialise Tidal within GHCI. 245 | A custom boot file can be specified using the `g:tidal_boot` variable. 246 | 247 | In the case that `g:tidal_boot` is unspecified, vim-tidal will traverse parent 248 | directories until one of either `BootTidal.hs`, `Tidal.ghci` or `boot.tidal` are 249 | found. 250 | 251 | If no tidal boot file can be found by traversing parent directories, tidal will 252 | check the `g:tidal_boot_fallback` variable for a fallback boot file. This 253 | variable is useful for specifying a default user-wide tidal boot file on your 254 | system, while still allowing each tidal project to optionally use their own 255 | dedicated, local tidal boot file. By default, `g:tidal_boot_fallback` will point 256 | to the `Tidal.ghci` file provided with this plugin. 257 | 258 | ### Default bindings ### 259 | 260 | By default, there are two normal keybindings and one for visual blocks using 261 | your `` key. If you don't have one defined, set it on your 262 | `.vimrc` script with `let maplocalleader=","`, for example. 263 | 264 | If you don't like some of the bindings or want to change them, add this line to 265 | disable them: 266 | 267 | ```vim 268 | let g:tidal_no_mappings = 1 269 | ``` 270 | 271 | See section Mappings on [ftplugin/tidal.vim](ftplugin/tidal.vim) and copy the 272 | bindings you like to your `.vimrc` file and modify them. 273 | 274 | ### Vim Terminal 275 | 276 | On both Vim (version 8 or above) and NeoVim, the default target in which we boot 277 | Tidal with GHCi is the native terminal. 278 | 279 | While it is the default, it can also be specified manually with the following: 280 | 281 | ```vim 282 | let g:tidal_target = "terminal" 283 | ``` 284 | 285 | Open a file with a `.tidal` suffix, write and send a line of code to tidal, and 286 | the tidal terminal will open in a window below your editor. 287 | 288 | Use standard vim window navigation controls to focus the terminal (ie ` down/up`) 289 | 290 | You can learn more about the native Vim terminal here: 291 | 292 | https://vimhelp.org/terminal.txt.html 293 | 294 | ### tmux (alternative to Vim terminal) 295 | 296 | Before Vim had native terminal support, this plugin provided a "tmux" target in 297 | order to allow for multiplexing the user's terminal via the 3rd party CLI tool. 298 | If you have `tmux` installed and you wish to use it instead of the native Vim 299 | terminal, you can enable this target with the following: 300 | 301 | ```vim 302 | let g:tidal_target = "tmux" 303 | ``` 304 | 305 | This target will be enabled automatically in the case that the version of Vim in 306 | use does not have native terminal support. 307 | 308 | You can configure tmux socket name and target pane by typing `c` 309 | or `:TidalConfig`. This will prompt you first for the socket name, then for 310 | the target pane. 311 | 312 | About the target pane: 313 | 314 | * `":"` means current window, current pane (a reasonable default) 315 | * `":i"` means the ith window, current pane 316 | * `":i.j"` means the ith window, jth pane 317 | * `"h:i.j"` means the tmux session where h is the session identifier (either 318 | session name or number), the ith window and the jth pane 319 | 320 | When you exit Vim you will lose that configuration. To make this permanent, set 321 | `g:tidal_default_config` on your `.vimrc`. For example, suppose you want to run 322 | Tidal on a tmux session named `omg`, and the GHCi interpreter will be running 323 | on the window 1 and pane 0. In that case you would need to add this line: 324 | 325 | ```vim 326 | let g:tidal_default_config = {"socket_name": "default", "target_pane": "omg:1.0"} 327 | ``` 328 | 329 | ### Optional Supercollider Terminal 330 | 331 | Vim-tidal provides an option for automatically running the supercollider 332 | command-line tool `sclang` alongside the Tidal GCHI terminal. By default this 333 | terminal is disabled, however it can be enabled with the following: 334 | 335 | ```vim 336 | let g:tidal_sc_enable = 1 337 | ``` 338 | 339 | This can be useful to avoid the need to manually run sclang in a separate 340 | terminal or to open the supercollider IDE. 341 | 342 | A custom supercollider boot file can be specified by assigning its path to the 343 | `g:tidal_sc_boot` variable. 344 | 345 | In the case that `g:tidal_sc_boot` is unspecified, vim-tidal will traverse 346 | parent directories until one of either `boot.sc` or `boot.scd` are found. 347 | 348 | If no supercollider boot file can be found by traversing parent directories, 349 | tidal will check the `g:tidal_sc_boot_fallback` variable for a fallback boot 350 | file. This variable is useful for specifying a default user-wide supercollider 351 | boot file on your system, while still allowing each tidal project to optionally 352 | use their own dedicated, local supercollider boot file. 353 | 354 | By default, `g:tidal_sc_boot_fallback` will point to the `boot.sc` file provided 355 | with this plugin which simply starts SuperDirt with the default settings. 356 | 357 | ### Miscellaneous ### 358 | 359 | When sending a paragraph or a single line, vim-tidal will "flash" the selection 360 | for some milliseconds. By default duration is set to 150ms, but you can modify 361 | it by setting the `g:tidal_flash_duration` variable. 362 | 363 | Write the paste buffer to an external text file: 364 | 365 | ```vim 366 | let g:tidal_paste_file = "/tmp/tidal_paste_file.txt" 367 | ``` 368 | 369 | For customizing the startup script for defining helper functions, see below. 370 | 371 | 372 | ## `tidalvim` and `tidal` ## 373 | 374 | `tidalvim` is just an example script. You can copy and customize it as much 375 | as you want. See `man tmux` if you want to know more about its options. 376 | 377 | For example, if you want to split horizontally instead of vertically, change 378 | the `-v` for `-h` option in the `split-window` line: 379 | 380 | ```diff 381 | - split-window -v -t $SESSION \; \ 382 | + split-window -h -t $SESSION \; \ 383 | ``` 384 | 385 | Both scripts have some options that you can specify as environment variables. 386 | For example: 387 | 388 | ``` 389 | TIDAL_TEMPO_IP=192.168.0.15 SESSION=whatever tidalvim 390 | ``` 391 | 392 | This would start Tidal synced to another Tidal on 192.168.0.15, and it would 393 | try to attach or create a tmux sesssion called `whatever`. 394 | 395 | The following is a list of all variables that can be changed: 396 | 397 | * `FILE`: File name to open with Vim (default: `$(date +%F).tidal`, e.g. 398 | `2017-03-09.tidal`). The `.tidal` extension is important (you can run 399 | `:setfiletype haskell.tidal` in case you won't use a .tidal file here). 400 | 401 | * `SESSION`: tmux session name (default: `tidal`) 402 | 403 | * `TIDAL_BOOT_PATH`: Tidal Bootstrap file, a .ghci file (default: `Tidal.ghci`) 404 | 405 | * `TIDAL_TEMPO_IP`: Tells Tidal to sync tempo with another Tidal instance on 406 | the specified IP (default: `127.0.0.1`, i.e. use local) 407 | 408 | * `VIM`: Vim command (default: `vim`) 409 | 410 | * `GHCI`: GHCi command (default: `ghci`) 411 | 412 | * `TMUX`: tmux command (default: `tmux`) 413 | 414 | ### Customizing Tidal startup ### 415 | 416 | In case you have defined some helper functions, and/or you want to import other 417 | modules into Tidal, you can edit the `Tidal.ghci` found at the root of the 418 | repository. 419 | 420 | However doing this could eventually cause conflicts when trying to upgrade 421 | vim-tidal, so instead I recommend that you define a different `.ghci` file that 422 | first loads `Tidal.ghci` and includes all your custom definitions. 423 | 424 | Here is an example. Suppose you define a `myStuff.ghci` file on your home 425 | directory like this: 426 | 427 | ```haskell 428 | --file: ~/myStuff.ghci 429 | 430 | -- Bootstrap Tidal 431 | -- Replace this path if you have vim-tidal installed elsewhere 432 | :script ~/.vim/bundle/vim-tidal/Tidal.ghci 433 | 434 | :{ 435 | let foo = every 4 $ within (0.75, 1) (density 4) 436 | bar = n "<0 1 2 4>" 437 | :} 438 | ``` 439 | 440 | Then, you would run `tidal` or `tidalvim` with `TIDAL_BOOT_PATH` pointing to 441 | your new script file: 442 | 443 | ```bash 444 | TIDAL_BOOT_PATH=~/myStuff.ghci tidalvim 445 | ``` 446 | 447 | Please note that this a `.ghci` script, not a Haskell module. So multiline 448 | definitions need to be wrapped around `:{` and `:}`, as shown in the example 449 | above. 450 | 451 | 452 | ## Troubleshooting 453 | 454 | Here is a list of common problems. 455 | 456 | > I press `` but it moves the screen down by one line, and nothing else happens 457 | 458 | Usually `` is used to move the screen forward by one line, but vim-tidal remaps 459 | this to sending current paragraph. If this is happening you either: 460 | 461 | 1. Opened a file without `.tidal` extension, or changed file type accidentally. 462 | *Solution*: Reopen Vim or set filetype for current buffer with `:set 463 | ft=tidal`. 464 | 2. Have `g:tidal_no_mappings` setting on your `.vimrc`. This disables all 465 | mappings. 466 | *Solution*: Remove `` binding, or rebind to something else. 467 | 468 | It could also be that you do not have `filetype plugin on` setting in your 469 | .vimrc. Make sure you have that setting defined. 470 | 471 | > I press `` and nothing else happens 472 | 473 | This means that vim-tidal is sending text to tmux, but to the wrong 474 | session/window/pane. 475 | *Solution*: Check that you have configure the socket name and target pane 476 | correctly. See the Configure section above for more information. 477 | 478 | If you have any question or something does not work as expected, there are many 479 | channels you can go to: 480 | 481 | * [Chat](https://talk.lurk.org/): Reach out at the `#tidal` and `#vim` channels 482 | * [GitHub issues](https://github.com/tidalcycles/vim-tidal/issues/new) 483 | * [Official Tidal forum](https://we.lurk.org/postorius/lists/tidal.we.lurk.org/) 484 | 485 | 486 | ## Contributing 487 | 488 | Bug reports and pull requests are welcome on GitHub at 489 | . This project is intended to be a 490 | safe, welcoming space for collaboration, and contributors are expected to 491 | adhere to the [Contributor Covenant](http://contributor-covenant.org) code of 492 | conduct. 493 | 494 | 495 | ## License 496 | 497 | Refer to the [LICENSE](LICENSE) file 498 | -------------------------------------------------------------------------------- /Tidal.ghci: -------------------------------------------------------------------------------- 1 | :set -XOverloadedStrings 2 | :set prompt "" 3 | 4 | import Sound.Tidal.Context 5 | 6 | import System.IO (hSetEncoding, stdout, utf8) 7 | hSetEncoding stdout utf8 8 | 9 | -- total latency = oLatency + cFrameTimespan 10 | tidal <- startTidal (superdirtTarget {oLatency = 0.1, oAddress = "127.0.0.1", oPort = 57120}) (defaultConfig {cVerbose = True, cFrameTimespan = 1/20}) 11 | 12 | :{ 13 | let only = (hush >>) 14 | p = streamReplace tidal 15 | hush = streamHush tidal 16 | panic = do hush 17 | once $ sound "superpanic" 18 | list = streamList tidal 19 | mute = streamMute tidal 20 | unmute = streamUnmute tidal 21 | unmuteAll = streamUnmuteAll tidal 22 | unsoloAll = streamUnsoloAll tidal 23 | solo = streamSolo tidal 24 | unsolo = streamUnsolo tidal 25 | once = streamOnce tidal 26 | first = streamFirst tidal 27 | asap = once 28 | nudgeAll = streamNudgeAll tidal 29 | all = streamAll tidal 30 | resetCycles = streamResetCycles tidal 31 | setcps = asap . cps 32 | getcps = streamGetcps tidal 33 | getnow = streamGetnow tidal 34 | xfade i = transition tidal True (Sound.Tidal.Transition.xfadeIn 4) i 35 | xfadeIn i t = transition tidal True (Sound.Tidal.Transition.xfadeIn t) i 36 | histpan i t = transition tidal True (Sound.Tidal.Transition.histpan t) i 37 | wait i t = transition tidal True (Sound.Tidal.Transition.wait t) i 38 | waitT i f t = transition tidal True (Sound.Tidal.Transition.waitT f t) i 39 | jump i = transition tidal True (Sound.Tidal.Transition.jump) i 40 | jumpIn i t = transition tidal True (Sound.Tidal.Transition.jumpIn t) i 41 | jumpIn' i t = transition tidal True (Sound.Tidal.Transition.jumpIn' t) i 42 | jumpMod i t = transition tidal True (Sound.Tidal.Transition.jumpMod t) i 43 | jumpMod' i t p = transition tidal True (Sound.Tidal.Transition.jumpMod' t p) i 44 | mortal i lifespan release = transition tidal True (Sound.Tidal.Transition.mortal lifespan release) i 45 | interpolate i = transition tidal True (Sound.Tidal.Transition.interpolate) i 46 | interpolateIn i t = transition tidal True (Sound.Tidal.Transition.interpolateIn t) i 47 | clutch i = transition tidal True (Sound.Tidal.Transition.clutch) i 48 | clutchIn i t = transition tidal True (Sound.Tidal.Transition.clutchIn t) i 49 | anticipate i = transition tidal True (Sound.Tidal.Transition.anticipate) i 50 | anticipateIn i t = transition tidal True (Sound.Tidal.Transition.anticipateIn t) i 51 | forId i t = transition tidal False (Sound.Tidal.Transition.mortalOverlay t) i 52 | d1 = p 1 . (|< orbit 0) 53 | d2 = p 2 . (|< orbit 1) 54 | d3 = p 3 . (|< orbit 2) 55 | d4 = p 4 . (|< orbit 3) 56 | d5 = p 5 . (|< orbit 4) 57 | d6 = p 6 . (|< orbit 5) 58 | d7 = p 7 . (|< orbit 6) 59 | d8 = p 8 . (|< orbit 7) 60 | d9 = p 9 . (|< orbit 8) 61 | d10 = p 10 . (|< orbit 9) 62 | d11 = p 11 . (|< orbit 10) 63 | d12 = p 12 . (|< orbit 11) 64 | d13 = p 13 65 | d14 = p 14 66 | d15 = p 15 67 | d16 = p 16 68 | :} 69 | 70 | :{ 71 | let getState = streamGet tidal 72 | setI = streamSetI tidal 73 | setF = streamSetF tidal 74 | setS = streamSetS tidal 75 | setR = streamSetR tidal 76 | setB = streamSetB tidal 77 | :} 78 | 79 | :set prompt "tidal> " 80 | :set prompt-cont "" 81 | 82 | default (Pattern String, Integer, Double) 83 | -------------------------------------------------------------------------------- /bin/generate-completions: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | samplepath=$1 4 | outputpath=$2 5 | 6 | ls "$samplepath" | sed 's/README.md//; s/Dirt-Samples.quark//;' | sed '/^\s*$/d' > "$outputpath" 7 | -------------------------------------------------------------------------------- /bin/tidal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euf -o pipefail 3 | 4 | GHCI=${GHCI:-"ghci"} 5 | TIDAL_DATA_DIR=$($GHCI -e Paths_tidal.getDataDir | sed 's/"//g') 6 | TIDAL_BOOT_PATH=${TIDAL_BOOT_PATH:-"$TIDAL_DATA_DIR/BootTidal.hs"} 7 | 8 | # Run GHCI and load Tidal bootstrap file 9 | $GHCI -ghci-script $TIDAL_BOOT_PATH "$@" 10 | -------------------------------------------------------------------------------- /bin/tidal.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Store the directory containing the script in a variable 4 | SET "source=%~dp0" 5 | 6 | REM Get path to vim-tidal bootfile 7 | SET "TIDAL_BOOT_PATH=%SOURCE%\..\Tidal.ghci" 8 | 9 | REM Launch Tidal Cycles 10 | ghci -ghci-script "%TIDAL_BOOT_PATH%" -------------------------------------------------------------------------------- /bin/tidalvim: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euf -o pipefail 3 | 4 | VIM=${VIM:-"vim"} 5 | TMUX=${TMUX:-"tmux"} 6 | 7 | FILE=${FILE:-"$(date +%F).tidal"} 8 | SESSION=${SESSION:-"tidal"} 9 | 10 | TIDAL_BOOT_PATH=${TIDAL_BOOT_PATH:-""} 11 | GHCI=${GHCI:-""} 12 | 13 | args=${@:-$FILE} 14 | 15 | # Check if tmux session "tidal" is running, attach only 16 | # else, create new session, split windows and run processes 17 | $TMUX -2 attach-session -t $SESSION || $TMUX -2 \ 18 | new-session -s $SESSION \; \ 19 | split-window -v -t $SESSION \; \ 20 | send-keys -t 0 "$VIM $args" C-m \; \ 21 | send-keys -t 1 "TIDAL_BOOT_PATH=$TIDAL_BOOT_PATH GHCI=$GHCI tidal" C-m \; \ 22 | select-pane -t 0 23 | -------------------------------------------------------------------------------- /boot.sc: -------------------------------------------------------------------------------- 1 | SuperDirt.start; 2 | -------------------------------------------------------------------------------- /ftdetect/tidal.vim: -------------------------------------------------------------------------------- 1 | autocmd BufRead,BufNewFile *.tidal setfiletype tidal 2 | -------------------------------------------------------------------------------- /ftplugin/tidal.vim: -------------------------------------------------------------------------------- 1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | " Functions 3 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 4 | 5 | let s:not_prefixable_keywords = [ "import", "data", "instance", "class", "{-#", "type", "case", "do", "let", "default", "foreign", "--"] 6 | 7 | " guess correct number of spaces to indent 8 | " (tabs are not allowed) 9 | function! Get_indent_string() 10 | return repeat(" ", 4) 11 | endfunction 12 | 13 | " replace tabs by spaces 14 | function! Tab_to_spaces(text) 15 | return substitute(a:text, " ", Get_indent_string(), "g") 16 | endfunction 17 | 18 | " Wrap in :{ :} if there's more than one line 19 | function! Wrap_if_multi(lines) 20 | if len(a:lines) > 1 21 | return [":{"] + a:lines + [":}"] 22 | else 23 | return a:lines 24 | endif 25 | endfunction 26 | 27 | " change string into array of lines 28 | function! Lines(text) 29 | return split(a:text, "\n") 30 | endfunction 31 | 32 | " change lines back into text 33 | function! Unlines(lines) 34 | if g:tidal_target == "tmux" 35 | " Without this, the user has to manually submit a newline each time 36 | " they evaluate an expression with `ctrl e`. 37 | return join(a:lines, "\n") . "\n" 38 | else 39 | return join(a:lines, "\n") 40 | endif 41 | endfunction 42 | 43 | " vim slime handler 44 | function! _EscapeText_tidal(text) 45 | let l:lines = Lines(Tab_to_spaces(a:text)) 46 | let l:lines = Wrap_if_multi(l:lines) 47 | return Unlines(l:lines) 48 | endfunction 49 | 50 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 51 | " Mappings 52 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 53 | 54 | if !exists("g:tidal_no_mappings") || !g:tidal_no_mappings 55 | if !hasmapto('TidalConfig', 'n') 56 | nmap c TidalConfig 57 | endif 58 | 59 | if !hasmapto('TidalRegionSend', 'x') 60 | xmap s TidalRegionSend 61 | xmap TidalRegionSend 62 | endif 63 | 64 | if !hasmapto('TidalLineSend', 'n') 65 | nmap s TidalLineSend 66 | endif 67 | 68 | if !hasmapto('TidalParagraphSend', 'n') 69 | nmap ss TidalParagraphSend 70 | nmap TidalParagraphSend 71 | endif 72 | 73 | imap TidalParagraphSendi 74 | 75 | nnoremap h :TidalHush 76 | nnoremap :TidalHush 77 | let i = 1 78 | while i <= 9 79 | execute 'nnoremap '.i.' :TidalSilence '.i.'' 80 | execute 'nnoremap :TidalSilence '.i.'' 81 | execute 'nnoremap s'.i.' :TidalPlay '.i.'' 82 | let i += 1 83 | endwhile 84 | endif 85 | -------------------------------------------------------------------------------- /plugin/tidal.vim: -------------------------------------------------------------------------------- 1 | if exists("g:loaded_tidal") || &cp || v:version < 700 2 | finish 3 | endif 4 | let g:loaded_tidal = 1 5 | let s:parent_path = fnamemodify(expand(""), ":p:h:s?/plugin??") 6 | 7 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 8 | " Default config 9 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 10 | 11 | " Attempts to find a tidal boot file in this or any parent directory. 12 | function s:FindTidalBoot() 13 | for name in ["BootTidal.hs", "Tidal.ghci", "boot.tidal"] 14 | let tidal_boot_file = findfile(name, ".".';') 15 | if !empty(tidal_boot_file) 16 | return tidal_boot_file 17 | endif 18 | endfor 19 | endfunction 20 | 21 | " Attempts to find a supercollider startup file in this or any parent dir. 22 | function s:FindScBoot() 23 | for name in ["boot.sc", "boot.scd"] 24 | let sc_boot_file = findfile(name, ".".';') 25 | if !empty(sc_boot_file) 26 | return sc_boot_file 27 | endif 28 | endfor 29 | endfunction 30 | 31 | if !exists("g:tidal_target") 32 | if has('nvim') || has('terminal') 33 | let g:tidal_target = "terminal" 34 | else 35 | let g:tidal_target = "tmux" 36 | endif 37 | endif 38 | 39 | if !exists("g:tidal_paste_file") 40 | let g:tidal_paste_file = tempname() 41 | endif 42 | 43 | if !exists("g:tidal_default_config") 44 | let g:tidal_default_config = { "socket_name": "default", "target_pane": ":0.1" } 45 | endif 46 | 47 | if !exists("g:tidal_preserve_curpos") 48 | let g:tidal_preserve_curpos = 1 49 | endif 50 | 51 | if !exists("g:tidal_flash_duration") 52 | let g:tidal_flash_duration = 150 53 | endif 54 | 55 | if !exists("g:tidal_ghci") 56 | let g:tidal_ghci = "ghci" 57 | endif 58 | 59 | if !exists("g:tidal_boot_fallback") 60 | let g:tidal_boot_fallback = s:parent_path . "/Tidal.ghci" 61 | endif 62 | 63 | if !exists("g:tidal_boot") 64 | let g:tidal_boot = s:FindTidalBoot() 65 | if empty(g:tidal_boot) 66 | let g:tidal_boot = g:tidal_boot_fallback 67 | endif 68 | endif 69 | 70 | if !exists("g:tidal_superdirt_enable") 71 | " Allow vim-tidal to automatically start SuperDirt. Disabled by default. 72 | let g:tidal_superdirt_enable = 0 73 | endif 74 | 75 | if !exists("g:tidal_sclang") 76 | let g:tidal_sclang = "sclang" 77 | endif 78 | 79 | " Allow vim-tidal to automatically start supercollider. Disabled by default. 80 | if !exists("g:tidal_sc_enable") 81 | let g:tidal_sc_enable = 0 82 | endif 83 | 84 | if !exists("g:tidal_sc_boot_fallback") 85 | let g:tidal_sc_boot_fallback = s:parent_path . "/boot.sc" 86 | endif 87 | 88 | if !exists("g:tidal_sc_boot") 89 | let g:tidal_sc_boot = s:FindScBoot() 90 | if empty(g:tidal_sc_boot) 91 | let g:tidal_sc_boot = g:tidal_sc_boot_fallback 92 | endif 93 | endif 94 | 95 | if !exists("g:tidal_sc_boot_cmd") 96 | " A command that can be run from the terminal to start supercollider. 97 | " The default assumes `SuperDirt` is installed. 98 | let g:tidal_sc_boot_cmd = g:tidal_sclang . " " . g:tidal_sc_boot 99 | endif 100 | 101 | if filereadable(s:parent_path . "/.dirt-samples") 102 | let &l:dictionary .= ',' . s:parent_path . "/.dirt-samples" 103 | endif 104 | 105 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 106 | " Tmux 107 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 108 | 109 | function! s:TmuxSend(config, text) 110 | let l:prefix = "tmux -L " . shellescape(a:config["socket_name"]) 111 | " use STDIN unless configured to use a file 112 | if !exists("g:tidal_paste_file") 113 | call system(l:prefix . " load-buffer -", a:text) 114 | else 115 | call s:WritePasteFile(a:text) 116 | call system(l:prefix . " load-buffer " . g:tidal_paste_file) 117 | end 118 | call system(l:prefix . " paste-buffer -d -t " . shellescape(a:config["target_pane"])) 119 | endfunction 120 | 121 | function! s:TmuxPaneNames(A,L,P) 122 | let format = '#{pane_id} #{session_name}:#{window_index}.#{pane_index} #{window_name}#{?window_active, (active),}' 123 | return system("tmux -L " . shellescape(b:tidal_config['socket_name']) . " list-panes -a -F " . shellescape(format)) 124 | endfunction 125 | 126 | function! s:TmuxConfig() abort 127 | if !exists("b:tidal_config") 128 | let b:tidal_config = {"socket_name": "default", "target_pane": ":"} 129 | end 130 | 131 | let b:tidal_config["socket_name"] = input("tmux socket name: ", b:tidal_config["socket_name"]) 132 | let b:tidal_config["target_pane"] = input("tmux target pane: ", b:tidal_config["target_pane"], "custom," . s:SID() . "_TmuxPaneNames") 133 | if b:tidal_config["target_pane"] =~ '\s\+' 134 | let b:tidal_config["target_pane"] = split(b:tidal_config["target_pane"])[0] 135 | endif 136 | endfunction 137 | 138 | 139 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 140 | " Terminal 141 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 142 | 143 | let s:tidal_term_ghci = -1 144 | let s:tidal_term_sc = -1 145 | 146 | " NVim and VIM8 Terminal Implementation 147 | " ===================================== 148 | function! s:TerminalOpen() 149 | if has('nvim') 150 | let current_win = winnr() 151 | 152 | if s:tidal_term_ghci == -1 153 | " force terminal split to open below current pane 154 | :exe "set splitbelow" 155 | execute "split term://" . g:tidal_ghci . " -ghci-script=" . g:tidal_boot 156 | let s:tidal_term_ghci = b:terminal_job_id 157 | 158 | " Give tidal a moment to start up so following commands can take effect 159 | sleep 500m 160 | 161 | " Make terminal scroll to follow output 162 | :exe "normal G" 163 | :exe "normal 10\_" 164 | endif 165 | 166 | if g:tidal_sc_enable == 1 && s:tidal_term_sc == -1 167 | execute "vsplit term://" . g:tidal_sc_boot_cmd 168 | let s:tidal_term_sc = b:terminal_job_id 169 | 170 | " Make terminal scroll to follow output 171 | :exe "normal G" 172 | endif 173 | 174 | execute current_win .. "wincmd w" 175 | elseif has('terminal') 176 | " Keep track of the current window number so we can switch back. 177 | let current_win = winnr() 178 | 179 | " Open a Terminal with GHCI with tidal booted. 180 | if s:tidal_term_ghci == -1 181 | execute "below split" 182 | let s:tidal_term_ghci = term_start((g:tidal_ghci . " -ghci-script=" . g:tidal_boot), #{ 183 | \ term_name: 'tidal', 184 | \ term_rows: 10, 185 | \ norestore: 1, 186 | \ curwin: 1, 187 | \ }) 188 | endif 189 | 190 | " Open a terminal with supercollider running. 191 | if g:tidal_sc_enable == 1 && s:tidal_term_sc == -1 192 | execute "vert split" 193 | let s:tidal_term_sc = term_start(g:tidal_sc_boot_cmd, #{ 194 | \ term_name: 'supercollider', 195 | \ term_rows: 10, 196 | \ norestore: 1, 197 | \ curwin: 1, 198 | \ }) 199 | endif 200 | 201 | " Return focus to the original window. 202 | execute current_win .. "wincmd w" 203 | endif 204 | endfunction 205 | 206 | function! s:TerminalSend(config, text) 207 | call s:TerminalOpen() 208 | if has('nvim') 209 | call jobsend(s:tidal_term_ghci, a:text . "\") 210 | elseif has('terminal') 211 | call term_sendkeys(s:tidal_term_ghci, a:text . "\") 212 | endif 213 | endfunction 214 | 215 | " These two are unnecessary AFAIK. 216 | function! s:TerminalPaneNames(A,L,P) 217 | endfunction 218 | function! s:TerminalConfig() abort 219 | endfunction 220 | 221 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 222 | " Helpers 223 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 224 | 225 | function! s:SID() 226 | return matchstr(expand(''), '\zs\d\+\ze_SID$') 227 | endfun 228 | 229 | function! s:WritePasteFile(text) 230 | " could check exists("*writefile") 231 | call system("cat > " . g:tidal_paste_file, a:text) 232 | endfunction 233 | 234 | function! s:_EscapeText(text) 235 | if exists("&filetype") 236 | let custom_escape = "_EscapeText_" . substitute(&filetype, "[.]", "_", "g") 237 | if exists("*" . custom_escape) 238 | let result = call(custom_escape, [a:text]) 239 | end 240 | end 241 | 242 | " use a:text if the ftplugin didn't kick in 243 | if !exists("result") 244 | let result = a:text 245 | end 246 | 247 | " return an array, regardless 248 | if type(result) == type("") 249 | return [result] 250 | else 251 | return result 252 | end 253 | endfunction 254 | 255 | function! s:TidalGetConfig() 256 | if !exists("b:tidal_config") 257 | if exists("g:tidal_default_config") 258 | let b:tidal_config = g:tidal_default_config 259 | else 260 | call s:TidalDispatch('Config') 261 | end 262 | end 263 | endfunction 264 | 265 | function! s:TidalFlashVisualSelection() 266 | " Redraw to show current visual selection, and sleep 267 | redraw 268 | execute "sleep " . g:tidal_flash_duration . " m" 269 | " Then leave visual mode 270 | silent exe "normal! vv" 271 | endfunction 272 | 273 | function! s:TidalSendOp(type, ...) abort 274 | call s:TidalGetConfig() 275 | 276 | let sel_save = &selection 277 | let &selection = "inclusive" 278 | let rv = getreg('"') 279 | let rt = getregtype('"') 280 | 281 | if a:0 " Invoked from Visual mode, use '< and '> marks. 282 | silent exe "normal! `<" . a:type . '`>y' 283 | elseif a:type == 'line' 284 | silent exe "normal! '[V']y" 285 | elseif a:type == 'block' 286 | silent exe "normal! `[\`]\y" 287 | else 288 | silent exe "normal! `[v`]y" 289 | endif 290 | 291 | call setreg('"', @", 'V') 292 | call s:TidalSend(@") 293 | 294 | " Flash selection 295 | if a:type == 'line' 296 | silent exe "normal! '[V']" 297 | call s:TidalFlashVisualSelection() 298 | endif 299 | 300 | let &selection = sel_save 301 | call setreg('"', rv, rt) 302 | 303 | call s:TidalRestoreCurPos() 304 | endfunction 305 | 306 | function! s:TidalSendRange() range abort 307 | call s:TidalGetConfig() 308 | 309 | let rv = getreg('"') 310 | let rt = getregtype('"') 311 | silent execute a:firstline . ',' . a:lastline . 'yank' 312 | call s:TidalSend(@") 313 | call setreg('"', rv, rt) 314 | endfunction 315 | 316 | function! s:TidalSendLines(count) abort 317 | call s:TidalGetConfig() 318 | 319 | let rv = getreg('"') 320 | let rt = getregtype('"') 321 | 322 | silent execute "normal! " . a:count . "yy" 323 | 324 | call s:TidalSend(@") 325 | call setreg('"', rv, rt) 326 | 327 | " Flash lines 328 | silent execute "normal! V" 329 | if a:count > 1 330 | silent execute "normal! " . (a:count - 1) . "\" 331 | endif 332 | call s:TidalFlashVisualSelection() 333 | endfunction 334 | 335 | function! s:TidalStoreCurPos() 336 | if g:tidal_preserve_curpos == 1 337 | if exists("*getcurpos") 338 | let s:cur = getcurpos() 339 | else 340 | let s:cur = getpos('.') 341 | endif 342 | endif 343 | endfunction 344 | 345 | function! s:TidalRestoreCurPos() 346 | if g:tidal_preserve_curpos == 1 347 | call setpos('.', s:cur) 348 | endif 349 | endfunction 350 | 351 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 352 | " Public interface 353 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 354 | 355 | function! s:TidalSend(text) 356 | call s:TidalGetConfig() 357 | 358 | let pieces = s:_EscapeText(a:text) 359 | for piece in pieces 360 | call s:TidalDispatch('Send', b:tidal_config, piece) 361 | endfor 362 | endfunction 363 | 364 | function! s:TidalConfig() abort 365 | call inputsave() 366 | call s:TidalDispatch('Config') 367 | call inputrestore() 368 | endfunction 369 | 370 | " delegation 371 | function! s:TidalDispatch(name, ...) 372 | let target = substitute(tolower(g:tidal_target), '\(.\)', '\u\1', '') " Capitalize 373 | return call("s:" . target . a:name, a:000) 374 | endfunction 375 | 376 | function! s:TidalHush() 377 | execute 'TidalSend1 hush' 378 | endfunction 379 | 380 | function! s:TidalSilence(stream) 381 | silent execute 'TidalSend1 d' . a:stream . ' silence' 382 | endfunction 383 | 384 | function! s:TidalPlay(stream) 385 | let res = search('^\s*d' . a:stream) 386 | if res > 0 387 | silent execute "normal! vip:TidalSend\" 388 | silent execute "normal! vip" 389 | call s:TidalFlashVisualSelection() 390 | else 391 | echo "d" . a:stream . " was not found" 392 | endif 393 | endfunction 394 | 395 | function! s:TidalGenerateCompletions(path) 396 | let l:exe = s:parent_path . "/bin/generate-completions" 397 | let l:output_path = s:parent_path . "/.dirt-samples" 398 | 399 | if !empty(a:path) 400 | let l:sample_path = a:path 401 | else 402 | if has('macunix') 403 | let l:sample_path = "~/Library/Application Support/SuperCollider/downloaded-quarks/Dirt-Samples" 404 | elseif has('unix') 405 | let l:sample_path = "~/.local/share/SuperCollider/downloaded-quarks/Dirt-Samples" 406 | endif 407 | endif 408 | " generate completion file 409 | silent execute '!' . l:exe shellescape(expand(l:sample_path)) shellescape(expand(l:output_path)) 410 | echo "Generated dictionary of dirt-samples" 411 | " setup completion 412 | let &l:dictionary .= ',' . l:output_path 413 | endfunction 414 | 415 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 416 | " Setup key bindings 417 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 418 | 419 | command -bar -nargs=0 TidalConfig call s:TidalConfig() 420 | command -range -bar -nargs=0 TidalSend ,call s:TidalSendRange() 421 | command -nargs=+ TidalSend1 call s:TidalSend() 422 | 423 | command! -nargs=0 TidalHush call s:TidalHush() 424 | command! -nargs=1 TidalSilence call s:TidalSilence() 425 | command! -nargs=1 TidalPlay call s:TidalPlay() 426 | command! -nargs=? TidalGenerateCompletions call s:TidalGenerateCompletions() 427 | 428 | noremap Operator :call TidalStoreCurPos():set opfunc=TidalSendOpg@ 429 | 430 | noremap