├── .gitignore ├── boot.sc ├── ftdetect └── tidal.vim ├── bin ├── generate-completions ├── tidal.bat ├── tidal └── tidalvim ├── Makefile ├── CHANGELOG ├── LICENSE ├── ftplugin └── tidal.vim ├── Tidal.ghci ├── CODE_OF_CONDUCT.md ├── syntax └── tidal.vim ├── plugin └── tidal.vim └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .dirt-samples 2 | -------------------------------------------------------------------------------- /boot.sc: -------------------------------------------------------------------------------- 1 | SuperDirt.start; 2 | -------------------------------------------------------------------------------- /ftdetect/tidal.vim: -------------------------------------------------------------------------------- 1 | autocmd BufRead,BufNewFile *.tidal setfiletype tidal 2 | -------------------------------------------------------------------------------- /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.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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /syntax/tidal.vim: -------------------------------------------------------------------------------- 1 | " syntax highlighting for tidal/haskell 2 | " 3 | " Very minor modifications from syntax file of 4 | " https://github.com/neovimhaskell/haskell-vim 5 | " 6 | " Heavily modified version of the haskell syntax 7 | " highlighter to support haskell. 8 | " 9 | " author: raichoo (raichoo@googlemail.com) 10 | 11 | if version < 600 12 | syn clear 13 | elseif exists("b:current_syntax") 14 | finish 15 | endif 16 | 17 | if get(g:, 'haskell_backpack', 0) 18 | syn keyword haskellBackpackStructure unit signature 19 | syn keyword haskellBackpackDependency dependency 20 | endif 21 | 22 | syn spell notoplevel 23 | syn match haskellRecordField contained containedin=haskellBlock 24 | \ "[_a-z][a-zA-Z0-9_']*\(,\s*[_a-z][a-zA-Z0-9_']*\)*\_s\+::\_s" 25 | \ contains= 26 | \ haskellIdentifier, 27 | \ haskellOperators, 28 | \ haskellSeparator, 29 | \ haskellParens 30 | syn match haskellTypeSig 31 | \ "^\s*\(where\s\+\|let\s\+\|default\s\+\)\?[_a-z][a-zA-Z0-9_']*#\?\(,\s*[_a-z][a-zA-Z0-9_']*#\?\)*\_s\+::\_s" 32 | \ contains= 33 | \ haskellWhere, 34 | \ haskellLet, 35 | \ haskellDefault, 36 | \ haskellIdentifier, 37 | \ haskellOperators, 38 | \ haskellSeparator, 39 | \ haskellParens 40 | syn keyword haskellWhere where 41 | syn keyword haskellLet let 42 | syn match HaskellDerive "\\(\s\+\<\(anyclass\|instance\|newtype\|stock\)\>\)\?" 43 | syn keyword haskellDeclKeyword module class instance newtype in 44 | syn match haskellDecl "\<\(type\|data\)\>\s\+\(\\)\?" 45 | syn keyword haskellDefault default 46 | syn keyword haskellImportKeywords import qualified safe as hiding contained 47 | syn keyword haskellForeignKeywords foreign export import ccall safe unsafe interruptible capi prim contained 48 | syn region haskellForeignImport start="\" end="\_s\+::\s" keepend 49 | \ contains= 50 | \ haskellString, 51 | \ haskellOperators, 52 | \ haskellForeignKeywords, 53 | \ haskellIdentifier 54 | syn match haskellImport "^\s*\\s\+\(\\s\+\)\?\(\\s\+\)\?.\+\(\s\+\\s\+.\+\)\?\(\s\+\\)\?" 55 | \ contains= 56 | \ haskellParens, 57 | \ haskellOperators, 58 | \ haskellImportKeywords, 59 | \ haskellType, 60 | \ haskellLineComment, 61 | \ haskellBlockComment, 62 | \ haskellString, 63 | \ haskellPragma 64 | syn keyword haskellKeyword do case of 65 | if get(g:, 'haskell_enable_static_pointers', 0) 66 | syn keyword haskellStatic static 67 | endif 68 | syn keyword haskellConditional if then else 69 | syn match haskellNumber "\<[0-9]\+\>\|\<0[xX][0-9a-fA-F]\+\>\|\<0[oO][0-7]\+\>\|\<0[bB][10]\+\>" 70 | syn match haskellFloat "\<[0-9]\+\.[0-9]\+\([eE][-+]\=[0-9]\+\)\=\>" 71 | syn match haskellSeparator "[,;]" 72 | syn region haskellParens matchgroup=haskellDelimiter start="(" end=")" contains=TOP,haskellTypeSig,@Spell 73 | syn region haskellBrackets matchgroup=haskellDelimiter start="\[" end="]" contains=TOP,haskellTypeSig,@Spell 74 | syn region haskellBlock matchgroup=haskellDelimiter start="{" end="}" contains=TOP,@Spell 75 | syn keyword haskellInfix infix infixl infixr 76 | syn keyword haskellBottom undefined error 77 | syn match haskellOperators "[-!#$%&\*\+/<=>\?@\\^|~:.]\+\|\<_\>" 78 | syn match haskellQuote "\<'\+" contained 79 | syn match haskellQuotedType "[A-Z][a-zA-Z0-9_']*\>" contained 80 | syn region haskellQuoted start="\<'\+" end="\>" 81 | \ contains= 82 | \ haskellType, 83 | \ haskellQuote, 84 | \ haskellQuotedType, 85 | \ haskellSeparator, 86 | \ haskellParens, 87 | \ haskellOperators, 88 | \ haskellIdentifier 89 | syn match haskellLineComment "---*\([^-!#$%&\*\+./<=>\?@\\^|~].*\)\?$" 90 | \ contains= 91 | \ haskellTodo, 92 | \ @Spell 93 | syn match haskellBacktick "`[A-Za-z_][A-Za-z0-9_\.']*#\?`" 94 | syn region haskellString start=+"+ skip=+\\\\\|\\"+ end=+"+ 95 | \ contains=@Spell 96 | syn match haskellIdentifier "[_a-z][a-zA-z0-9_']*" contained 97 | syn match haskellChar "\<'[^'\\]'\|'\\.'\|'\\u[0-9a-fA-F]\{4}'\>" 98 | syn match haskellType "\<[A-Z][a-zA-Z0-9_']*\>" 99 | syn region haskellBlockComment start="{-" end="-}" 100 | \ contains= 101 | \ haskellBlockComment, 102 | \ haskellTodo, 103 | \ @Spell 104 | syn region haskellPragma start="{-#" end="#-}" 105 | syn region haskellLiquid start="{-@" end="@-}" 106 | "syn match haskellPreProc "^#.*$" 107 | syn keyword haskellTodo TODO FIXME contained 108 | " Treat a shebang line at the start of the file as a comment 109 | syn match haskellShebang "\%^#!.*$" 110 | if !get(g:, 'haskell_disable_TH', 0) 111 | syn match haskellQuasiQuoted "." containedin=haskellQuasiQuote contained 112 | syn region haskellQuasiQuote matchgroup=haskellTH start="\[[_a-zA-Z][a-zA-z0-9._']*|" end="|\]" 113 | syn region haskellTHBlock matchgroup=haskellTH start="\[\(d\|t\|p\)\?|" end="|]" contains=TOP 114 | syn region haskellTHDoubleBlock matchgroup=haskellTH start="\[||" end="||]" contains=TOP 115 | endif 116 | if get(g:, 'haskell_enable_typeroles', 0) 117 | syn keyword haskellTypeRoles phantom representational nominal contained 118 | syn region haskellTypeRoleBlock matchgroup=haskellTypeRoles start="type\s\+role" end="$" keepend 119 | \ contains= 120 | \ haskellType, 121 | \ haskellTypeRoles 122 | endif 123 | if get(g:, 'haskell_enable_quantification', 0) 124 | syn keyword haskellForall forall 125 | endif 126 | if get(g:, 'haskell_enable_recursivedo', 0) 127 | syn keyword haskellRecursiveDo mdo rec 128 | endif 129 | if get(g:, 'haskell_enable_arrowsyntax', 0) 130 | syn keyword haskellArrowSyntax proc 131 | endif 132 | if get(g:, 'haskell_enable_pattern_synonyms', 0) 133 | syn keyword haskellPatternKeyword pattern 134 | endif 135 | 136 | highlight def link haskellBottom Macro 137 | highlight def link haskellTH Boolean 138 | highlight def link haskellIdentifier Identifier 139 | highlight def link haskellForeignKeywords Structure 140 | highlight def link haskellKeyword Keyword 141 | highlight def link haskellDefault Keyword 142 | highlight def link haskellConditional Conditional 143 | highlight def link haskellNumber Number 144 | highlight def link haskellFloat Float 145 | highlight def link haskellSeparator Delimiter 146 | highlight def link haskellDelimiter Delimiter 147 | highlight def link haskellInfix Keyword 148 | highlight def link haskellOperators Operator 149 | highlight def link haskellQuote Operator 150 | highlight def link haskellShebang Comment 151 | highlight def link haskellLineComment Comment 152 | highlight def link haskellBlockComment Comment 153 | highlight def link haskellPragma SpecialComment 154 | highlight def link haskellLiquid SpecialComment 155 | highlight def link haskellString String 156 | highlight def link haskellChar String 157 | highlight def link haskellBacktick Operator 158 | highlight def link haskellQuasiQuoted String 159 | highlight def link haskellTodo Todo 160 | "highlight def link haskellPreProc PreProc 161 | highlight def link haskellAssocType Type 162 | highlight def link haskellQuotedType Type 163 | highlight def link haskellType Type 164 | highlight def link haskellImportKeywords Include 165 | if get(g:, 'haskell_classic_highlighting', 0) 166 | highlight def link haskellDeclKeyword Keyword 167 | highlight def link HaskellDerive Keyword 168 | highlight def link haskellDecl Keyword 169 | highlight def link haskellWhere Keyword 170 | highlight def link haskellLet Keyword 171 | else 172 | highlight def link haskellDeclKeyword Structure 173 | highlight def link HaskellDerive Structure 174 | highlight def link haskellDecl Structure 175 | highlight def link haskellWhere Structure 176 | highlight def link haskellLet Structure 177 | endif 178 | 179 | if get(g:, 'haskell_enable_quantification', 0) 180 | highlight def link haskellForall Operator 181 | endif 182 | if get(g:, 'haskell_enable_recursivedo', 0) 183 | highlight def link haskellRecursiveDo Keyword 184 | endif 185 | if get(g:, 'haskell_enable_arrowsyntax', 0) 186 | highlight def link haskellArrowSyntax Keyword 187 | endif 188 | if get(g:, 'haskell_enable_static_pointers', 0) 189 | highlight def link haskellStatic Keyword 190 | endif 191 | if get(g:, 'haskell_classic_highlighting', 0) 192 | if get(g:, 'haskell_enable_pattern_synonyms', 0) 193 | highlight def link haskellPatternKeyword Keyword 194 | endif 195 | if get(g:, 'haskell_enable_typeroles', 0) 196 | highlight def link haskellTypeRoles Keyword 197 | endif 198 | else 199 | if get(g:, 'haskell_enable_pattern_synonyms', 0) 200 | highlight def link haskellPatternKeyword Structure 201 | endif 202 | if get(g:, 'haskell_enable_typeroles', 0) 203 | highlight def link haskellTypeRoles Structure 204 | endif 205 | endif 206 | 207 | if get(g:, 'haskell_backpack', 0) 208 | highlight def link haskellBackpackStructure Structure 209 | highlight def link haskellBackpackDependency Include 210 | endif 211 | 212 | let b:current_syntax = "tidal" 213 | -------------------------------------------------------------------------------- /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