Repository: tidalcycles/vim-tidal Branch: master Commit: e440fe5bdfe0 Files: 16 Total size: 49.1 KB Directory structure: gitextract_odl0_omb/ ├── .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 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .dirt-samples ================================================ FILE: CHANGELOG ================================================ 0.3.3 * Fix syntax highlighting issues with the `#` operator 0.3.2 * Update README 0.3.1 * Add mapping for sending paragraphs, on Normal, Visual and Insert modes * Flash text selection when sending text * Force tmux to use 256 colors on tidalvim * Add maping as an alternative to h * Try to use getcurpos() if available when restoring cursor position 0.3.0 * Update bootstrap script for Tidal 0.9 * `tidalvim` now only creates 2 panes for Vim and GHCi * Split `tidalvim` script in two scripts: `tidal` and `tidalvim` * Better instructions for customizing Tidal bootstrap script 0.2.0 * Remove vim-slime dependency * Preserve cursor position when sending to tmux * Configure tmux automatically with defaults 0.1.0 * Use Slime to communicate via tmux * Create "tidalvim" script for creating Dirt and Tidal instances * Define more vim-like default bindings * Add bindings to silence channels * Add bindings for playing first occurrence of stream ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at munshkr@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Damián Emiliano Silvani Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ mkfile_path := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) prefix=/usr/local install: ln -fs $(mkfile_path)/bin/tidal $(prefix)/bin ln -fs $(mkfile_path)/bin/tidalvim $(prefix)/bin uninstall: rm -f $(prefix)/bin/tidal $(prefix)/bin/tidalvim .PHONY: install uninstall ================================================ FILE: README.md ================================================ # vim-tidal # A Vim/NeoVim plugin for [TidalCycles](http://tidalcycles.org), the language for live coding musical patterns written in Haskell. This plugin by default uses [tmux](https://tmux.github.io/), a known and loved terminal multiplexer, for communicating with between Vim and the Tidal interpreter. It was originally based on [vim-slime](https://github.com/jpalardy/vim-slime). ![](http://i.imgur.com/frOLFFI.gif) If you are using Vim8 or NeoVim, you can use the native Terminal feature instead of tmux. Read the Configuration section on how to enable it. [![asciicast](https://asciinema.org/a/224891.svg)](https://asciinema.org/a/224891) ## Getting Started ## 1. Start livecoding with Vim by simply running: ```bash $ tidalvim ``` This creates a tmux session with Vim and Tidal running on different panes. 2. Write something like this: ```haskell d1 $ sound "bd sn" ``` 3. While being on that line, press `` (Control + E) to evaluate it. You should see Vim flash that line for a second and a chunk of text appear on your Tidal interpreter. If you already have SuperDirt or other synth running, you should hear a kick and a snare :) ## Install ## Make sure you have TidalCycles installed, with SuperDirt running. See [the Tidal wiki](https://tidalcycles.org/index.php/Userbase) for more information. ### Install tmux ### #### Ubuntu/Debian #### You can install it from the main repos: $ sudo apt-get install tmux #### OSX #### $ brew install tmux #### Windows #### There seems to be [a Cygwin package for tmux](https://cygwin.com/cgi-bin2/package-cat.cgi?file=x86%2Ftmux%2Ftmux-1.9a-1&grep=tmux), 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). ### Install plugin ### I recommend using a Vim plugin manager like [Plug](https://github.com/junegunn/vim-plug). Check the link for instructions on installing and configuring. If you don't want a plugin manager, you can also download the latest release [here](https://github.com/tidalcycles/vim-tidal/releases) and extract the contents on your Vim directory (usually `~/.vim/`). For example, with Plug you need to: * Edit your `.vimrc` file and add these lines: ```vim Plug 'tidalcycles/vim-tidal' ``` * Restart Vim and execute `:PlugInstall` to automatically download and install the plugins. #### UNIX-based Systems #### If you are on a UNIX-based operating system (Linux distributions, MacOS, etc.), go to the plugin repository and run `make install`: (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) $ cd ~/.vim/plugged/vim-tidal $ sudo make install This creates symlinks on `/usr/local/bin` for `tidal` and `tidalvim` scripts. You can remove them later if you want with `make uninstall`. #### Windows #### :warning: **This plugin has only been tested on Windows 10 using Neovim >0.5** If you are on Windows, add the `vim-tidal\bin` directory to your `PATH` user environment variable: 1. Click the `Start` button 2. Type "Edit the system environment variables" and hit `enter` or click on the search result 3. Click the button labeled `Environment variables...` 4. In the `User variables for [username]` table, click the entry for the `Path` variable, followed by the `Edit...` button beneath the same table 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. 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. #### Final Installation Note #### Make sure to have the `filetype plugin on` setting on your .vimrc, otherwise plugin won't be loaded when opening a .tidal file. ### Older Tidal versions (pre 1.0) ### Tidal 1.0 introduces some breaking changes, so if haven't upgraded yet, you can still use this plugin with an older version. Just point your Plug entry to use the `tidal-0.9` branch. First change your Plug line on your `.vimrc` to: ```vim Plug 'tidalcycles/vim-tidal', {'branch': 'tidal-0.9'} ``` Then on Vim run `:PlugInstall` to update your plugin. ## Usage This plugin comes bundled with two Bash scripts: `tidalvim` and `tidal`. ### `tidalvim` `tidalvim` starts a tmux session with the screen horizontally splitted, having Vim on the upper pane and the Tidal interpreter on the lower pane. This is the simplest way to start using Tidal with Vim. You don't have to use `tidalvim` necessarily. If you have a more complex setup or just want to use Vim outside of tmux, you can use `tidal`. See below. ### `tidal` `tidal` fires up GHCi (the Glasgow Haskell interpreter) and runs a bootstrap file that loads Tidal up. `tidalvim` uses this script to start the Tidal interpreter on the lower pane. You can even use it standalone (without Vim) by simply running `tidal` from your shell. ```haskell $ tidal GHCi, version 7.10.3: http://www.haskell.org/ghc/ :? for help tidal> d1 $ sound "bd sn" tidal> :t density 2 $ n "0 1" density 2 $ n "0 1" :: Pattern ParamMap ``` So, in case you don't want to use `tidalvim`, just run the following on another terminal: ```bash tmux new-session -s tidal tidal ``` What `tidal` does is actually run `ghci` with the argument `-ghci-script Tidal.ghci`. [Tidal.ghci](Tidal.ghci) is found at the root of the repository, and is responsible for bootstraping Tidal. See Configure section for more on how to customize Tidal bootstraping process. Any extra arguments when running `tidal` will be delegated to `ghci`. ### Commands These are some of the commands that can be run from Vim command line: * `:TidalSend`: Send a `[range]` of lines. If no range is provided the current line is sent. * `:TidalSend1 {text}`: Send a single line of text specified on the command line. * `:TidalConfig`: Configure tmux socket name and target pane * `:TidalSilence [num]`: Silence stream number `[num]` by sending `d[num] silence`. * `:TidalPlay [num]`: Send first ocurrence of stream number `[num`] from the current cursor position. * `:TidalHush`: Silences all streams by sending `hush`. * `:TidalGenerateCompletions {path}`: Generate dictionary for Dirt-Samples completion (path is optional). ### Default bindings Using one of these key bindings you can send lines to Tidal: * `` (Control+E), `ss`: Send current inner paragraph. * `s`: Send current line or current visually selected block. `` can be called on either Normal, Visual, Select or Insert mode, so it is probably easier to type than `ss` or `s`. There are other bindings to control Tidal like: * `s[num]`: Call `:TidalPlay [num]` * `[num]`: Call `:TidalSilence [num]` * `h`, ``: Call `:TidalHush` #### About `` The `` key is a special key used to perform commands with a sequence of keys. The `` key behaves as the `` key, but is *local* to a buffer. In particular, the above bindings only work in buffers with the "tidal" file type set, e.g. files whose file type is `.tidal` By default, there is no `` set. To define one, e.g. for use with a comma (`,`), write this on your `.vimrc` file: ```vim let maplocalleader="," ``` Reload your configuration (or restart Vim), and after typing `,ss` on a few lines of code, you should see those being copied onto the Tidal interpreter on the lower pane. ## Configure ## ### GHCI By default, `vim-tidal` uses the globally installed GHCI to launch the REPL. If you have installed Tidal through Stack (`stack install tidal`) or some other means, you can specify another command to use with `g:tidal_ghci`. For example, if one installed Tidal with Stack, they would use: ```vim let g:tidal_ghci = "stack exec ghci --" ``` ### Tidal Boot File A "Tidal boot file" is a file that may be used to initialise Tidal within GHCI. A custom boot file can be specified using the `g:tidal_boot` variable. In the case that `g:tidal_boot` is unspecified, vim-tidal will traverse parent directories until one of either `BootTidal.hs`, `Tidal.ghci` or `boot.tidal` are found. If no tidal boot file can be found by traversing parent directories, tidal will check the `g:tidal_boot_fallback` variable for a fallback boot file. This variable is useful for specifying a default user-wide tidal boot file on your system, while still allowing each tidal project to optionally use their own dedicated, local tidal boot file. By default, `g:tidal_boot_fallback` will point to the `Tidal.ghci` file provided with this plugin. ### Default bindings ### By default, there are two normal keybindings and one for visual blocks using your `` key. If you don't have one defined, set it on your `.vimrc` script with `let maplocalleader=","`, for example. If you don't like some of the bindings or want to change them, add this line to disable them: ```vim let g:tidal_no_mappings = 1 ``` See section Mappings on [ftplugin/tidal.vim](ftplugin/tidal.vim) and copy the bindings you like to your `.vimrc` file and modify them. ### Vim Terminal On both Vim (version 8 or above) and NeoVim, the default target in which we boot Tidal with GHCi is the native terminal. While it is the default, it can also be specified manually with the following: ```vim let g:tidal_target = "terminal" ``` Open a file with a `.tidal` suffix, write and send a line of code to tidal, and the tidal terminal will open in a window below your editor. Use standard vim window navigation controls to focus the terminal (ie ` down/up`) You can learn more about the native Vim terminal here: https://vimhelp.org/terminal.txt.html ### tmux (alternative to Vim terminal) Before Vim had native terminal support, this plugin provided a "tmux" target in order to allow for multiplexing the user's terminal via the 3rd party CLI tool. If you have `tmux` installed and you wish to use it instead of the native Vim terminal, you can enable this target with the following: ```vim let g:tidal_target = "tmux" ``` This target will be enabled automatically in the case that the version of Vim in use does not have native terminal support. You can configure tmux socket name and target pane by typing `c` or `:TidalConfig`. This will prompt you first for the socket name, then for the target pane. About the target pane: * `":"` means current window, current pane (a reasonable default) * `":i"` means the ith window, current pane * `":i.j"` means the ith window, jth pane * `"h:i.j"` means the tmux session where h is the session identifier (either session name or number), the ith window and the jth pane When you exit Vim you will lose that configuration. To make this permanent, set `g:tidal_default_config` on your `.vimrc`. For example, suppose you want to run Tidal on a tmux session named `omg`, and the GHCi interpreter will be running on the window 1 and pane 0. In that case you would need to add this line: ```vim let g:tidal_default_config = {"socket_name": "default", "target_pane": "omg:1.0"} ``` ### Optional Supercollider Terminal Vim-tidal provides an option for automatically running the supercollider command-line tool `sclang` alongside the Tidal GCHI terminal. By default this terminal is disabled, however it can be enabled with the following: ```vim let g:tidal_sc_enable = 1 ``` This can be useful to avoid the need to manually run sclang in a separate terminal or to open the supercollider IDE. A custom supercollider boot file can be specified by assigning its path to the `g:tidal_sc_boot` variable. In the case that `g:tidal_sc_boot` is unspecified, vim-tidal will traverse parent directories until one of either `boot.sc` or `boot.scd` are found. If no supercollider boot file can be found by traversing parent directories, tidal will check the `g:tidal_sc_boot_fallback` variable for a fallback boot file. This variable is useful for specifying a default user-wide supercollider boot file on your system, while still allowing each tidal project to optionally use their own dedicated, local supercollider boot file. By default, `g:tidal_sc_boot_fallback` will point to the `boot.sc` file provided with this plugin which simply starts SuperDirt with the default settings. ### Miscellaneous ### When sending a paragraph or a single line, vim-tidal will "flash" the selection for some milliseconds. By default duration is set to 150ms, but you can modify it by setting the `g:tidal_flash_duration` variable. Write the paste buffer to an external text file: ```vim let g:tidal_paste_file = "/tmp/tidal_paste_file.txt" ``` For customizing the startup script for defining helper functions, see below. ## `tidalvim` and `tidal` ## `tidalvim` is just an example script. You can copy and customize it as much as you want. See `man tmux` if you want to know more about its options. For example, if you want to split horizontally instead of vertically, change the `-v` for `-h` option in the `split-window` line: ```diff - split-window -v -t $SESSION \; \ + split-window -h -t $SESSION \; \ ``` Both scripts have some options that you can specify as environment variables. For example: ``` TIDAL_TEMPO_IP=192.168.0.15 SESSION=whatever tidalvim ``` This would start Tidal synced to another Tidal on 192.168.0.15, and it would try to attach or create a tmux sesssion called `whatever`. The following is a list of all variables that can be changed: * `FILE`: File name to open with Vim (default: `$(date +%F).tidal`, e.g. `2017-03-09.tidal`). The `.tidal` extension is important (you can run `:setfiletype haskell.tidal` in case you won't use a .tidal file here). * `SESSION`: tmux session name (default: `tidal`) * `TIDAL_BOOT_PATH`: Tidal Bootstrap file, a .ghci file (default: `Tidal.ghci`) * `TIDAL_TEMPO_IP`: Tells Tidal to sync tempo with another Tidal instance on the specified IP (default: `127.0.0.1`, i.e. use local) * `VIM`: Vim command (default: `vim`) * `GHCI`: GHCi command (default: `ghci`) * `TMUX`: tmux command (default: `tmux`) ### Customizing Tidal startup ### In case you have defined some helper functions, and/or you want to import other modules into Tidal, you can edit the `Tidal.ghci` found at the root of the repository. However doing this could eventually cause conflicts when trying to upgrade vim-tidal, so instead I recommend that you define a different `.ghci` file that first loads `Tidal.ghci` and includes all your custom definitions. Here is an example. Suppose you define a `myStuff.ghci` file on your home directory like this: ```haskell --file: ~/myStuff.ghci -- Bootstrap Tidal -- Replace this path if you have vim-tidal installed elsewhere :script ~/.vim/bundle/vim-tidal/Tidal.ghci :{ let foo = every 4 $ within (0.75, 1) (density 4) bar = n "<0 1 2 4>" :} ``` Then, you would run `tidal` or `tidalvim` with `TIDAL_BOOT_PATH` pointing to your new script file: ```bash TIDAL_BOOT_PATH=~/myStuff.ghci tidalvim ``` Please note that this a `.ghci` script, not a Haskell module. So multiline definitions need to be wrapped around `:{` and `:}`, as shown in the example above. ## Troubleshooting Here is a list of common problems. > I press `` but it moves the screen down by one line, and nothing else happens Usually `` is used to move the screen forward by one line, but vim-tidal remaps this to sending current paragraph. If this is happening you either: 1. Opened a file without `.tidal` extension, or changed file type accidentally. *Solution*: Reopen Vim or set filetype for current buffer with `:set ft=tidal`. 2. Have `g:tidal_no_mappings` setting on your `.vimrc`. This disables all mappings. *Solution*: Remove `` binding, or rebind to something else. It could also be that you do not have `filetype plugin on` setting in your .vimrc. Make sure you have that setting defined. > I press `` and nothing else happens This means that vim-tidal is sending text to tmux, but to the wrong session/window/pane. *Solution*: Check that you have configure the socket name and target pane correctly. See the Configure section above for more information. If you have any question or something does not work as expected, there are many channels you can go to: * [Chat](https://talk.lurk.org/): Reach out at the `#tidal` and `#vim` channels * [GitHub issues](https://github.com/tidalcycles/vim-tidal/issues/new) * [Official Tidal forum](https://we.lurk.org/postorius/lists/tidal.we.lurk.org/) ## Contributing Bug reports and pull requests are welcome on GitHub at . This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. ## License Refer to the [LICENSE](LICENSE) file ================================================ FILE: Tidal.ghci ================================================ :set -XOverloadedStrings :set prompt "" import Sound.Tidal.Context import System.IO (hSetEncoding, stdout, utf8) hSetEncoding stdout utf8 -- total latency = oLatency + cFrameTimespan tidal <- startTidal (superdirtTarget {oLatency = 0.1, oAddress = "127.0.0.1", oPort = 57120}) (defaultConfig {cVerbose = True, cFrameTimespan = 1/20}) :{ let only = (hush >>) p = streamReplace tidal hush = streamHush tidal panic = do hush once $ sound "superpanic" list = streamList tidal mute = streamMute tidal unmute = streamUnmute tidal unmuteAll = streamUnmuteAll tidal unsoloAll = streamUnsoloAll tidal solo = streamSolo tidal unsolo = streamUnsolo tidal once = streamOnce tidal first = streamFirst tidal asap = once nudgeAll = streamNudgeAll tidal all = streamAll tidal resetCycles = streamResetCycles tidal setcps = asap . cps getcps = streamGetcps tidal getnow = streamGetnow tidal xfade i = transition tidal True (Sound.Tidal.Transition.xfadeIn 4) i xfadeIn i t = transition tidal True (Sound.Tidal.Transition.xfadeIn t) i histpan i t = transition tidal True (Sound.Tidal.Transition.histpan t) i wait i t = transition tidal True (Sound.Tidal.Transition.wait t) i waitT i f t = transition tidal True (Sound.Tidal.Transition.waitT f t) i jump i = transition tidal True (Sound.Tidal.Transition.jump) i jumpIn i t = transition tidal True (Sound.Tidal.Transition.jumpIn t) i jumpIn' i t = transition tidal True (Sound.Tidal.Transition.jumpIn' t) i jumpMod i t = transition tidal True (Sound.Tidal.Transition.jumpMod t) i jumpMod' i t p = transition tidal True (Sound.Tidal.Transition.jumpMod' t p) i mortal i lifespan release = transition tidal True (Sound.Tidal.Transition.mortal lifespan release) i interpolate i = transition tidal True (Sound.Tidal.Transition.interpolate) i interpolateIn i t = transition tidal True (Sound.Tidal.Transition.interpolateIn t) i clutch i = transition tidal True (Sound.Tidal.Transition.clutch) i clutchIn i t = transition tidal True (Sound.Tidal.Transition.clutchIn t) i anticipate i = transition tidal True (Sound.Tidal.Transition.anticipate) i anticipateIn i t = transition tidal True (Sound.Tidal.Transition.anticipateIn t) i forId i t = transition tidal False (Sound.Tidal.Transition.mortalOverlay t) i d1 = p 1 . (|< orbit 0) d2 = p 2 . (|< orbit 1) d3 = p 3 . (|< orbit 2) d4 = p 4 . (|< orbit 3) d5 = p 5 . (|< orbit 4) d6 = p 6 . (|< orbit 5) d7 = p 7 . (|< orbit 6) d8 = p 8 . (|< orbit 7) d9 = p 9 . (|< orbit 8) d10 = p 10 . (|< orbit 9) d11 = p 11 . (|< orbit 10) d12 = p 12 . (|< orbit 11) d13 = p 13 d14 = p 14 d15 = p 15 d16 = p 16 :} :{ let getState = streamGet tidal setI = streamSetI tidal setF = streamSetF tidal setS = streamSetS tidal setR = streamSetR tidal setB = streamSetB tidal :} :set prompt "tidal> " :set prompt-cont "" default (Pattern String, Integer, Double) ================================================ FILE: bin/generate-completions ================================================ #!/usr/bin/env sh samplepath=$1 outputpath=$2 ls "$samplepath" | sed 's/README.md//; s/Dirt-Samples.quark//;' | sed '/^\s*$/d' > "$outputpath" ================================================ FILE: bin/tidal ================================================ #!/usr/bin/env bash set -euf -o pipefail GHCI=${GHCI:-"ghci"} TIDAL_DATA_DIR=$($GHCI -e Paths_tidal.getDataDir | sed 's/"//g') TIDAL_BOOT_PATH=${TIDAL_BOOT_PATH:-"$TIDAL_DATA_DIR/BootTidal.hs"} # Run GHCI and load Tidal bootstrap file $GHCI -ghci-script $TIDAL_BOOT_PATH "$@" ================================================ FILE: bin/tidal.bat ================================================ @ECHO OFF REM Store the directory containing the script in a variable SET "source=%~dp0" REM Get path to vim-tidal bootfile SET "TIDAL_BOOT_PATH=%SOURCE%\..\Tidal.ghci" REM Launch Tidal Cycles ghci -ghci-script "%TIDAL_BOOT_PATH%" ================================================ FILE: bin/tidalvim ================================================ #!/usr/bin/env bash set -euf -o pipefail VIM=${VIM:-"vim"} TMUX=${TMUX:-"tmux"} FILE=${FILE:-"$(date +%F).tidal"} SESSION=${SESSION:-"tidal"} TIDAL_BOOT_PATH=${TIDAL_BOOT_PATH:-""} GHCI=${GHCI:-""} args=${@:-$FILE} # Check if tmux session "tidal" is running, attach only # else, create new session, split windows and run processes $TMUX -2 attach-session -t $SESSION || $TMUX -2 \ new-session -s $SESSION \; \ split-window -v -t $SESSION \; \ send-keys -t 0 "$VIM $args" C-m \; \ send-keys -t 1 "TIDAL_BOOT_PATH=$TIDAL_BOOT_PATH GHCI=$GHCI tidal" C-m \; \ select-pane -t 0 ================================================ FILE: boot.sc ================================================ SuperDirt.start; ================================================ FILE: ftdetect/tidal.vim ================================================ autocmd BufRead,BufNewFile *.tidal setfiletype tidal ================================================ FILE: ftplugin/tidal.vim ================================================ """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " Functions """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" let s:not_prefixable_keywords = [ "import", "data", "instance", "class", "{-#", "type", "case", "do", "let", "default", "foreign", "--"] " guess correct number of spaces to indent " (tabs are not allowed) function! Get_indent_string() return repeat(" ", 4) endfunction " replace tabs by spaces function! Tab_to_spaces(text) return substitute(a:text, " ", Get_indent_string(), "g") endfunction " Wrap in :{ :} if there's more than one line function! Wrap_if_multi(lines) if len(a:lines) > 1 return [":{"] + a:lines + [":}"] else return a:lines endif endfunction " change string into array of lines function! Lines(text) return split(a:text, "\n") endfunction " change lines back into text function! Unlines(lines) if g:tidal_target == "tmux" " Without this, the user has to manually submit a newline each time " they evaluate an expression with `ctrl e`. return join(a:lines, "\n") . "\n" else return join(a:lines, "\n") endif endfunction " vim slime handler function! _EscapeText_tidal(text) let l:lines = Lines(Tab_to_spaces(a:text)) let l:lines = Wrap_if_multi(l:lines) return Unlines(l:lines) endfunction """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " Mappings """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" if !exists("g:tidal_no_mappings") || !g:tidal_no_mappings if !hasmapto('TidalConfig', 'n') nmap c TidalConfig endif if !hasmapto('TidalRegionSend', 'x') xmap s TidalRegionSend xmap TidalRegionSend endif if !hasmapto('TidalLineSend', 'n') nmap s TidalLineSend endif if !hasmapto('TidalParagraphSend', 'n') nmap ss TidalParagraphSend nmap TidalParagraphSend endif imap TidalParagraphSendi nnoremap h :TidalHush nnoremap :TidalHush let i = 1 while i <= 9 execute 'nnoremap '.i.' :TidalSilence '.i.'' execute 'nnoremap :TidalSilence '.i.'' execute 'nnoremap s'.i.' :TidalPlay '.i.'' let i += 1 endwhile endif ================================================ FILE: plugin/tidal.vim ================================================ if exists("g:loaded_tidal") || &cp || v:version < 700 finish endif let g:loaded_tidal = 1 let s:parent_path = fnamemodify(expand(""), ":p:h:s?/plugin??") """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " Default config """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " Attempts to find a tidal boot file in this or any parent directory. function s:FindTidalBoot() for name in ["BootTidal.hs", "Tidal.ghci", "boot.tidal"] let tidal_boot_file = findfile(name, ".".';') if !empty(tidal_boot_file) return tidal_boot_file endif endfor endfunction " Attempts to find a supercollider startup file in this or any parent dir. function s:FindScBoot() for name in ["boot.sc", "boot.scd"] let sc_boot_file = findfile(name, ".".';') if !empty(sc_boot_file) return sc_boot_file endif endfor endfunction if !exists("g:tidal_target") if has('nvim') || has('terminal') let g:tidal_target = "terminal" else let g:tidal_target = "tmux" endif endif if !exists("g:tidal_paste_file") let g:tidal_paste_file = tempname() endif if !exists("g:tidal_default_config") let g:tidal_default_config = { "socket_name": "default", "target_pane": ":0.1" } endif if !exists("g:tidal_preserve_curpos") let g:tidal_preserve_curpos = 1 endif if !exists("g:tidal_flash_duration") let g:tidal_flash_duration = 150 endif if !exists("g:tidal_ghci") let g:tidal_ghci = "ghci" endif if !exists("g:tidal_boot_fallback") let g:tidal_boot_fallback = s:parent_path . "/Tidal.ghci" endif if !exists("g:tidal_boot") let g:tidal_boot = s:FindTidalBoot() if empty(g:tidal_boot) let g:tidal_boot = g:tidal_boot_fallback endif endif if !exists("g:tidal_superdirt_enable") " Allow vim-tidal to automatically start SuperDirt. Disabled by default. let g:tidal_superdirt_enable = 0 endif if !exists("g:tidal_sclang") let g:tidal_sclang = "sclang" endif " Allow vim-tidal to automatically start supercollider. Disabled by default. if !exists("g:tidal_sc_enable") let g:tidal_sc_enable = 0 endif if !exists("g:tidal_sc_boot_fallback") let g:tidal_sc_boot_fallback = s:parent_path . "/boot.sc" endif if !exists("g:tidal_sc_boot") let g:tidal_sc_boot = s:FindScBoot() if empty(g:tidal_sc_boot) let g:tidal_sc_boot = g:tidal_sc_boot_fallback endif endif if !exists("g:tidal_sc_boot_cmd") " A command that can be run from the terminal to start supercollider. " The default assumes `SuperDirt` is installed. let g:tidal_sc_boot_cmd = g:tidal_sclang . " " . g:tidal_sc_boot endif if filereadable(s:parent_path . "/.dirt-samples") let &l:dictionary .= ',' . s:parent_path . "/.dirt-samples" endif """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " Tmux """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" function! s:TmuxSend(config, text) let l:prefix = "tmux -L " . shellescape(a:config["socket_name"]) " use STDIN unless configured to use a file if !exists("g:tidal_paste_file") call system(l:prefix . " load-buffer -", a:text) else call s:WritePasteFile(a:text) call system(l:prefix . " load-buffer " . g:tidal_paste_file) end call system(l:prefix . " paste-buffer -d -t " . shellescape(a:config["target_pane"])) endfunction function! s:TmuxPaneNames(A,L,P) let format = '#{pane_id} #{session_name}:#{window_index}.#{pane_index} #{window_name}#{?window_active, (active),}' return system("tmux -L " . shellescape(b:tidal_config['socket_name']) . " list-panes -a -F " . shellescape(format)) endfunction function! s:TmuxConfig() abort if !exists("b:tidal_config") let b:tidal_config = {"socket_name": "default", "target_pane": ":"} end let b:tidal_config["socket_name"] = input("tmux socket name: ", b:tidal_config["socket_name"]) let b:tidal_config["target_pane"] = input("tmux target pane: ", b:tidal_config["target_pane"], "custom," . s:SID() . "_TmuxPaneNames") if b:tidal_config["target_pane"] =~ '\s\+' let b:tidal_config["target_pane"] = split(b:tidal_config["target_pane"])[0] endif endfunction """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " Terminal """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" let s:tidal_term_ghci = -1 let s:tidal_term_sc = -1 " NVim and VIM8 Terminal Implementation " ===================================== function! s:TerminalOpen() if has('nvim') let current_win = winnr() if s:tidal_term_ghci == -1 " force terminal split to open below current pane :exe "set splitbelow" execute "split term://" . g:tidal_ghci . " -ghci-script=" . g:tidal_boot let s:tidal_term_ghci = b:terminal_job_id " Give tidal a moment to start up so following commands can take effect sleep 500m " Make terminal scroll to follow output :exe "normal G" :exe "normal 10\_" endif if g:tidal_sc_enable == 1 && s:tidal_term_sc == -1 execute "vsplit term://" . g:tidal_sc_boot_cmd let s:tidal_term_sc = b:terminal_job_id " Make terminal scroll to follow output :exe "normal G" endif execute current_win .. "wincmd w" elseif has('terminal') " Keep track of the current window number so we can switch back. let current_win = winnr() " Open a Terminal with GHCI with tidal booted. if s:tidal_term_ghci == -1 execute "below split" let s:tidal_term_ghci = term_start((g:tidal_ghci . " -ghci-script=" . g:tidal_boot), #{ \ term_name: 'tidal', \ term_rows: 10, \ norestore: 1, \ curwin: 1, \ }) endif " Open a terminal with supercollider running. if g:tidal_sc_enable == 1 && s:tidal_term_sc == -1 execute "vert split" let s:tidal_term_sc = term_start(g:tidal_sc_boot_cmd, #{ \ term_name: 'supercollider', \ term_rows: 10, \ norestore: 1, \ curwin: 1, \ }) endif " Return focus to the original window. execute current_win .. "wincmd w" endif endfunction function! s:TerminalSend(config, text) call s:TerminalOpen() if has('nvim') call jobsend(s:tidal_term_ghci, a:text . "\") elseif has('terminal') call term_sendkeys(s:tidal_term_ghci, a:text . "\") endif endfunction " These two are unnecessary AFAIK. function! s:TerminalPaneNames(A,L,P) endfunction function! s:TerminalConfig() abort endfunction """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " Helpers """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" function! s:SID() return matchstr(expand(''), '\zs\d\+\ze_SID$') endfun function! s:WritePasteFile(text) " could check exists("*writefile") call system("cat > " . g:tidal_paste_file, a:text) endfunction function! s:_EscapeText(text) if exists("&filetype") let custom_escape = "_EscapeText_" . substitute(&filetype, "[.]", "_", "g") if exists("*" . custom_escape) let result = call(custom_escape, [a:text]) end end " use a:text if the ftplugin didn't kick in if !exists("result") let result = a:text end " return an array, regardless if type(result) == type("") return [result] else return result end endfunction function! s:TidalGetConfig() if !exists("b:tidal_config") if exists("g:tidal_default_config") let b:tidal_config = g:tidal_default_config else call s:TidalDispatch('Config') end end endfunction function! s:TidalFlashVisualSelection() " Redraw to show current visual selection, and sleep redraw execute "sleep " . g:tidal_flash_duration . " m" " Then leave visual mode silent exe "normal! vv" endfunction function! s:TidalSendOp(type, ...) abort call s:TidalGetConfig() let sel_save = &selection let &selection = "inclusive" let rv = getreg('"') let rt = getregtype('"') if a:0 " Invoked from Visual mode, use '< and '> marks. silent exe "normal! `<" . a:type . '`>y' elseif a:type == 'line' silent exe "normal! '[V']y" elseif a:type == 'block' silent exe "normal! `[\`]\y" else silent exe "normal! `[v`]y" endif call setreg('"', @", 'V') call s:TidalSend(@") " Flash selection if a:type == 'line' silent exe "normal! '[V']" call s:TidalFlashVisualSelection() endif let &selection = sel_save call setreg('"', rv, rt) call s:TidalRestoreCurPos() endfunction function! s:TidalSendRange() range abort call s:TidalGetConfig() let rv = getreg('"') let rt = getregtype('"') silent execute a:firstline . ',' . a:lastline . 'yank' call s:TidalSend(@") call setreg('"', rv, rt) endfunction function! s:TidalSendLines(count) abort call s:TidalGetConfig() let rv = getreg('"') let rt = getregtype('"') silent execute "normal! " . a:count . "yy" call s:TidalSend(@") call setreg('"', rv, rt) " Flash lines silent execute "normal! V" if a:count > 1 silent execute "normal! " . (a:count - 1) . "\" endif call s:TidalFlashVisualSelection() endfunction function! s:TidalStoreCurPos() if g:tidal_preserve_curpos == 1 if exists("*getcurpos") let s:cur = getcurpos() else let s:cur = getpos('.') endif endif endfunction function! s:TidalRestoreCurPos() if g:tidal_preserve_curpos == 1 call setpos('.', s:cur) endif endfunction """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " Public interface """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" function! s:TidalSend(text) call s:TidalGetConfig() let pieces = s:_EscapeText(a:text) for piece in pieces call s:TidalDispatch('Send', b:tidal_config, piece) endfor endfunction function! s:TidalConfig() abort call inputsave() call s:TidalDispatch('Config') call inputrestore() endfunction " delegation function! s:TidalDispatch(name, ...) let target = substitute(tolower(g:tidal_target), '\(.\)', '\u\1', '') " Capitalize return call("s:" . target . a:name, a:000) endfunction function! s:TidalHush() execute 'TidalSend1 hush' endfunction function! s:TidalSilence(stream) silent execute 'TidalSend1 d' . a:stream . ' silence' endfunction function! s:TidalPlay(stream) let res = search('^\s*d' . a:stream) if res > 0 silent execute "normal! vip:TidalSend\" silent execute "normal! vip" call s:TidalFlashVisualSelection() else echo "d" . a:stream . " was not found" endif endfunction function! s:TidalGenerateCompletions(path) let l:exe = s:parent_path . "/bin/generate-completions" let l:output_path = s:parent_path . "/.dirt-samples" if !empty(a:path) let l:sample_path = a:path else if has('macunix') let l:sample_path = "~/Library/Application Support/SuperCollider/downloaded-quarks/Dirt-Samples" elseif has('unix') let l:sample_path = "~/.local/share/SuperCollider/downloaded-quarks/Dirt-Samples" endif endif " generate completion file silent execute '!' . l:exe shellescape(expand(l:sample_path)) shellescape(expand(l:output_path)) echo "Generated dictionary of dirt-samples" " setup completion let &l:dictionary .= ',' . l:output_path endfunction """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " Setup key bindings """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" command -bar -nargs=0 TidalConfig call s:TidalConfig() command -range -bar -nargs=0 TidalSend ,call s:TidalSendRange() command -nargs=+ TidalSend1 call s:TidalSend() command! -nargs=0 TidalHush call s:TidalHush() command! -nargs=1 TidalSilence call s:TidalSilence() command! -nargs=1 TidalPlay call s:TidalPlay() command! -nargs=? TidalGenerateCompletions call s:TidalGenerateCompletions() noremap Operator :call TidalStoreCurPos():set opfunc=TidalSendOpg@ noremap