├── .editorconfig ├── .gitignore ├── .travis.yml ├── .vintrc.yaml ├── CHANGELOG.md ├── README.md ├── after └── ftplugin │ ├── haskell │ └── ghci.vim │ └── lhaskell │ └── ghci.vim ├── autoload └── ghci │ ├── loc.vim │ ├── maker.vim │ ├── process.vim │ ├── repl.vim │ └── util.vim ├── doc └── ghci.txt ├── ghci.py └── plugin └── ghci.vim /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*.vim] 5 | indent_style = space 6 | indent_size = 4 7 | tab_width = 4 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | max_line_length = 80 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | vimproc 3 | tinytest 4 | verbose.log 5 | stdout.log 6 | *.pyc 7 | *.egg* 8 | .cache 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "2.7" 5 | - "3.3" 6 | - "3.4" 7 | - "3.5" 8 | - "3.6" 9 | - "nightly" 10 | 11 | matrix: 12 | fast_finish: true 13 | allow_failures: 14 | - python: "nightly" 15 | 16 | before_install: 17 | - pip install -IU pip pipenv 18 | 19 | install: 20 | - pipenv install --dev 21 | 22 | script: 23 | - vint . 24 | - pylama 25 | - pytest 26 | -------------------------------------------------------------------------------- /.vintrc.yaml: -------------------------------------------------------------------------------- 1 | cmdargs: 2 | severity: style_problem 3 | env: { neovim: true } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | * v0.1.1 4 | - Make the GHCi command configurable with `g:gchi_command`. 5 | * v0.1.0 6 | - Converted the fork of intero-neovim to use `cabal new-repl`, and stripped 7 | the Intero-specific features out. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | **THIS FORK HAS BEEN INTEGRATED BACK INTO [ITS UPSTREAM](https://github.com/parsonsmatt/intero-neovim) AND IS NO LONGER MAINTAINED!** 4 | 5 | If you are using this plugin since before the deprecation, it's recommended you switch over to https://github.com/parsonsmatt/intero-neovim. [It now has support for using any GHCi as a backend](https://github.com/parsonsmatt/intero-neovim#using-a-custom-backend), the primary feature of this plugin when it was forked. Thanks for your usage and support! 6 | 7 |
8 |
9 |
10 | 11 |
12 |

neovim-ghci

13 |

Interactive Haskell development using GHCi in Neovim

14 |
15 |
16 | 17 | This is a fork of [intero-neovim][] that uses regular GHCi, instead of 18 | `intero`. It has fewer features than its Intero counterpart, but does not rely 19 | on Stack and Intero. 20 | 21 | Some key features: 22 | 23 | - **On-the-fly Typechecking** 24 | 25 | This plugin reports errors and warnings as you work on your file using the 26 | Neomake plugin. Errors appear asynchronously, and don't block the UI. 27 | 28 | - **Built-in REPL** 29 | 30 | Work with your Haskell code directly in GHCi using Neovim `:terminal` buffers. 31 | Load your file and play around with top-level functions directly. 32 | 33 | ## Demo 34 | 35 | [![asciicast](https://asciinema.org/a/q9I5eNblDLCoOiQlZjm1ce0ba.png)](https://asciinema.org/a/q9I5eNblDLCoOiQlZjm1ce0ba?size=20&speed=3&theme=tango) 36 | 37 | ## Installing 38 | 39 | This plugin is compatible with `pathogen`, `vim-plug`, etc. For example: 40 | 41 | ```viml 42 | Plug 'owickstrom/neovim-ghci' 43 | ``` 44 | 45 | This plugin requires [Cabal][], 1.24.0 or higher. Optionally, install 46 | [Neomake][] for error reporting. 47 | 48 | 49 | ## Quickstart 50 | 51 | - To open the REPL: 52 | - `:GhciOpen` 53 | - To load into the REPL: 54 | - `:GhciLoadCurrentFile` 55 | - To reload whatever's in the REPL: 56 | - `:GhciReload` 57 | - To evaluate an expression from outside the REPL: 58 | - `:GhciEvaluate `, or 59 | - `:GhciEvaluate`, and then enter the expression in the prompt. 60 | 61 | ## Usage 62 | 63 | Complete usage and configuration information can be found in here: 64 | 65 | ```vim 66 | :help ghci 67 | ``` 68 | 69 | ## Example Configuration 70 | 71 | These are some suggested settings. This plugin sets up no keybindings by 72 | default. 73 | 74 | ```vim 75 | augroup ghciMaps 76 | au! 77 | " Maps for ghci. Restrict to Haskell buffers so the bindings don't collide. 78 | 79 | " Background process and window management 80 | au FileType haskell nnoremap gs :GhciStart 81 | au FileType haskell nnoremap gk :GhciKill 82 | 83 | " Restarting GHCi might be required if you add new dependencies 84 | au FileType haskell nnoremap gr :GhciRestart 85 | 86 | " Open GHCi split horizontally 87 | au FileType haskell nnoremap go :GhciOpen 88 | " Open GHCi split vertically 89 | au FileType haskell nnoremap gov :GhciOpenH 90 | au FileType haskell nnoremap gh :GhciHide 91 | 92 | " RELOADING (PICK ONE): 93 | 94 | " Automatically reload on save 95 | au BufWritePost *.hs GhciReload 96 | " Manually save and reload 97 | au FileType haskell nnoremap wr :w \| :GhciReload 98 | 99 | " Load individual modules 100 | au FileType haskell nnoremap gl :GhciLoadCurrentModule 101 | au FileType haskell nnoremap gf :GhciLoadCurrentFile 102 | augroup END 103 | 104 | " GHCi starts automatically. Set this if you'd like to prevent that. 105 | let g:ghci_start_immediately = 0 106 | 107 | " Customize how to run GHCi 108 | let g:ghci_command = 'cabal new-repl' 109 | let g:ghci_command_line_options = '-fobject-code' 110 | ``` 111 | 112 | ### Using the Stack REPL 113 | 114 | If you'd like to use `stack repl`, instead of plain `ghci` or `cabal repl`, you 115 | can use something like the following configuration: 116 | 117 | ``` vim 118 | let g:ghci_command = 'stack repl' 119 | let g:ghci_command_line_options = '--ghci-options="-fobject-code"' 120 | ``` 121 | 122 | Using a [project specific 123 | .nvim.rc](https://andrew.stwrt.ca/posts/project-specific-vimrc/), you can also 124 | customize the Stack targets for the GHCi session of particular projects: 125 | 126 | ``` vim 127 | let g:ghci_command = 'stack repl my-project:test:my-test-suite' 128 | ``` 129 | 130 | ## Caveats 131 | 132 | - Running `:Neomake!` directly will not work. You need to run `:GhciReload` 133 | instead. 134 | 135 | - Some commands may have unexpected side-effects if you have an autocommand 136 | that automatically switches to insert mode when entering a terminal buffer. 137 | 138 | ## Contributing 139 | 140 | This project welcomes new contributions! Submit pull requests and open issues 141 | on GitHub: https://github.com/owickstrom/neovim-ghci 142 | 143 | ## License 144 | 145 | [BSD3 License](http://www.opensource.org/licenses/BSD-3-Clause), the same 146 | license as ghcmod-vim and [intero-neovim][]. 147 | 148 | [intero-neovim]: https://github.com/parsonsmatt/intero-neovim 149 | [Cabal]: http://cabal.readthedocs.io/en/latest/ 150 | [Neomake]: https://github.com/neomake/neomake 151 | -------------------------------------------------------------------------------- /after/ftplugin/haskell/ghci.vim: -------------------------------------------------------------------------------- 1 | if exists('b:did_ftplugin_ghci') && b:did_ftplugin_ghci 2 | finish 3 | endif 4 | let b:did_ftplugin_ghci = 1 5 | 6 | if !exists('g:ghci_start_immediately') 7 | let g:ghci_start_immediately = 1 8 | endif 9 | 10 | if !exists('g:ghci_command') 11 | let g:ghci_command = 'ghci' 12 | endif 13 | 14 | if g:ghci_start_immediately 15 | call ghci#process#start() 16 | endif 17 | 18 | if exists('b:undo_ftplugin') 19 | let b:undo_ftplugin .= ' | ' 20 | else 21 | let b:undo_ftplugin = '' 22 | endif 23 | 24 | let b:undo_ftplugin .= 'unlet b:did_ftplugin_ghci' 25 | 26 | " vim: set ts=4 sw=4 et fdm=marker: 27 | -------------------------------------------------------------------------------- /after/ftplugin/lhaskell/ghci.vim: -------------------------------------------------------------------------------- 1 | ../haskell/intero.vim -------------------------------------------------------------------------------- /autoload/ghci/loc.vim: -------------------------------------------------------------------------------- 1 | function! ghci#loc#detect_module() abort "{{{ 2 | let l:regex = '^\C>\=\s*module\s\+\zs[A-Za-z0-9.]\+' 3 | for l:lineno in range(1, line('$')) 4 | let l:line = getline(l:lineno) 5 | let l:pos = match(l:line, l:regex) 6 | if l:pos != -1 7 | let l:synname = synIDattr(synID(l:lineno, l:pos+1, 0), 'name') 8 | if l:synname !~# 'Comment' 9 | return matchstr(l:line, l:regex) 10 | endif 11 | endif 12 | let l:lineno += 1 13 | endfor 14 | return 'Main' 15 | endfunction "}}} 16 | -------------------------------------------------------------------------------- /autoload/ghci/maker.vim: -------------------------------------------------------------------------------- 1 | """""""""" 2 | " Maker: 3 | " 4 | " This file contains code for integrating with Neomake. 5 | """""""""" 6 | 7 | " This is where we store the build log for consumption by Neomake. 8 | let s:log_file = tempname() 9 | 10 | function! ghci#maker#get_log_file() abort 11 | " Getter for log file path 12 | 13 | return s:log_file 14 | endfunction 15 | 16 | function! ghci#maker#write_update(lines) abort 17 | " Writes the specified lines to the log file, then notifies Neomake 18 | 19 | call writefile(a:lines, s:log_file) 20 | 21 | if g:ghci_use_neomake && exists(':NeomakeProject') 22 | NeomakeProject ghci 23 | endif 24 | endfunction 25 | 26 | function! ghci#maker#cleanup() abort 27 | call system('rm -f ' . s:log_file) 28 | endfunction 29 | 30 | -------------------------------------------------------------------------------- /autoload/ghci/process.vim: -------------------------------------------------------------------------------- 1 | """"""""""" 2 | " Process: 3 | " 4 | " This file contains functions for working with the GHCi process. This 5 | " includes ensuring that GHCi is installed, starting/killing the 6 | " process, and hiding/showing the REPL. 7 | """"""""""" 8 | 9 | " Lines of output consistuting of a command and the response to it 10 | let s:current_response = [] 11 | 12 | " The current (incomplete) line 13 | let s:current_line = '' 14 | 15 | " Whether GHCi has finished starting yet 16 | let g:ghci_started = 0 17 | 18 | " Whether GHCi has done its initialization yet 19 | let s:ghci_initialized = 0 20 | 21 | " If true, echo the next response. Reset after each response. 22 | let g:ghci_echo_next = 0 23 | 24 | " Queue of functions to run when a response is received. For a given response, 25 | " only the first will be run, after which it will be dropped from the queue. 26 | let s:response_handlers = [] 27 | 28 | function! ghci#process#initialize() abort 29 | " This function initializes GHCi. 30 | 31 | " We only need to initialize once 32 | if s:ghci_initialized 33 | return 34 | endif 35 | 36 | if g:ghci_use_neomake && !exists(':Neomake') 37 | echom 'Neomake not detected. Flychecking will be disabled.' 38 | endif 39 | 40 | " Load Python code 41 | py import sys 42 | call pyeval('sys.path.append("' . g:ghci_plugin_root . '")') 43 | py import ghci 44 | 45 | let s:ghci_initialized = 1 46 | endfunction 47 | 48 | function! ghci#process#start() abort 49 | " This is the entry point. It ensures that GHCi is initialized, then 50 | " starts an ghci terminal buffer. Initially only occupies a small area. 51 | " Returns the ghci buffer id. 52 | 53 | call ghci#process#initialize() 54 | 55 | if !exists('g:ghci_buffer_id') 56 | let g:ghci_buffer_id = s:start_buffer(10) 57 | endif 58 | 59 | augroup close_ghci 60 | autocmd! 61 | autocmd VimLeavePre * call ghci#process#kill() 62 | autocmd VimLeave * call ghci#maker#cleanup() 63 | augroup END 64 | 65 | return g:ghci_buffer_id 66 | endfunction 67 | 68 | function! ghci#process#kill() abort 69 | " Kills the ghci buffer, if it exists. 70 | if exists('g:ghci_buffer_id') 71 | exe 'bd! ' . g:ghci_buffer_id 72 | unlet g:ghci_buffer_id 73 | " Deleting a terminal buffer implicitly stops the job 74 | unlet g:ghci_job_id 75 | let g:ghci_started = 0 76 | endif 77 | endfunction 78 | 79 | function! ghci#process#hide() abort 80 | " Hides the current buffer without killing the process. 81 | silent! call s:hide_buffer() 82 | endfunction 83 | 84 | function! ghci#process#open() abort 85 | " Opens the GHCi REPL. If the REPL isn't currently running, then this 86 | " creates it. If the REPL is already running, this is a noop. Returns the 87 | " window ID. 88 | call ghci#process#initialize() 89 | 90 | let l:ghci_win = ghci#util#get_ghci_window() 91 | if l:ghci_win != -1 92 | return l:ghci_win 93 | elseif exists('g:ghci_buffer_id') 94 | let l:current_window = winnr() 95 | silent! call s:open_window(10) 96 | exe 'silent! buffer ' . g:ghci_buffer_id 97 | normal! G 98 | exe 'silent! ' . l:current_window . 'wincmd w' 99 | else 100 | let l:rc = ghci#process#start() 101 | if l:rc < 0 102 | return 103 | endif 104 | return ghci#process#open() 105 | endif 106 | endfunction 107 | 108 | function! ghci#process#add_handler(func) abort 109 | " Adds an event handler to the queue 110 | let s:response_handlers = s:response_handlers + [a:func] 111 | endfunction 112 | 113 | function! ghci#process#restart() abort 114 | call ghci#process#kill() 115 | call ghci#process#start() 116 | endfunction 117 | 118 | """""""""" 119 | " Private: 120 | """""""""" 121 | 122 | function! s:start_buffer(height) abort 123 | " Starts an GHCi REPL in a split below the current buffer. Returns the 124 | " ID of the buffer. 125 | exe 'below ' . a:height . ' split' 126 | 127 | let l:invocation = g:ghci_command 128 | if exists('g:ghci_command_line_options') 129 | let l:invocation .= ' ' . g:ghci_command_line_options 130 | endif 131 | 132 | enew 133 | silent call termopen(l:invocation, { 134 | \ 'on_stdout': function('s:on_stdout'), 135 | \ 'cwd': '.' 136 | \ }) 137 | 138 | silent file GHCi 139 | set bufhidden=hide 140 | set noswapfile 141 | set hidden 142 | let l:buffer_id = bufnr('%') 143 | let g:ghci_job_id = b:terminal_job_id 144 | quit 145 | call feedkeys("\") 146 | return l:buffer_id 147 | endfunction 148 | 149 | function! s:on_stdout(jobid, lines, event) abort 150 | if !exists('g:ghci_prompt_regex') 151 | let g:ghci_prompt_regex = '[^-]> ' 152 | endif 153 | 154 | for l:line_seg in a:lines 155 | let s:current_line = s:current_line . l:line_seg 156 | 157 | " If we've found a newline, flush the line buffer 158 | if s:current_line =~# '\r$' 159 | " Remove trailing newline, control chars 160 | let s:current_line = substitute(s:current_line, '\r$', '', '') 161 | let s:current_line = pyeval('ghci.strip_control_chars("s:current_line")') 162 | 163 | " Flush line buffer 164 | let s:current_response = s:current_response + [s:current_line] 165 | let s:current_line = '' 166 | endif 167 | 168 | " If the current line is a prompt, we just completed a response. 169 | " Note that we need to strip control chars here, because otherwise 170 | " they're only removed when the line is added to the response. 171 | if pyeval('ghci.strip_control_chars("s:current_line")') =~ (g:ghci_prompt_regex . '$') 172 | if len(s:current_response) > 0 173 | " Separate the input command from the response 174 | let l:cmd = substitute(s:current_response[0], '.*' . g:ghci_prompt_regex, '', '') 175 | call s:new_response(l:cmd, s:current_response[1:]) 176 | endif 177 | 178 | let s:current_response = [] 179 | endif 180 | 181 | endfor 182 | endfunction 183 | 184 | function! s:new_response(cmd, response) abort 185 | let l:initial_compile = 0 186 | 187 | " This means that GHCi is now available to run commands 188 | if !g:ghci_started 189 | echom 'GHCi ready' 190 | let g:ghci_started = 1 191 | let l:initial_compile = 1 192 | endif 193 | 194 | " For debugging 195 | let g:ghci_response = a:response 196 | 197 | " These handlers are used for all events 198 | if g:ghci_echo_next 199 | echo join(a:response, "\n") 200 | let g:ghci_echo_next = 0 201 | endif 202 | 203 | if(l:initial_compile || a:cmd =~# ':reload') 204 | " Trigger Neomake's parsing of the compilation errors 205 | call ghci#maker#write_update(a:response) 206 | endif 207 | 208 | " If a handler has been registered, pop it and run it 209 | if len(s:response_handlers) > 0 210 | call s:response_handlers[0](a:response) 211 | let s:response_handlers = s:response_handlers[1:] 212 | endif 213 | endfunction 214 | 215 | function! s:open_window(height) abort 216 | " Opens a window of a:height and moves it to the very bottom. 217 | exe 'below ' . a:height . ' split' 218 | normal! J 219 | endfunction 220 | 221 | function! s:hide_buffer() abort 222 | " This closes the GHCi REPL buffer without killing the process. 223 | if !s:ghci_initialized 224 | " GHCi was never started. 225 | return 226 | endif 227 | 228 | let l:window_number = ghci#util#get_ghci_window() 229 | if l:window_number > 0 230 | exec 'silent! ' . l:window_number . 'wincmd c' 231 | endif 232 | endfunction 233 | -------------------------------------------------------------------------------- /autoload/ghci/repl.vim: -------------------------------------------------------------------------------- 1 | """""""""" 2 | " Repl: 3 | " 4 | " This file contains code for sending commands to the GHCi REPL. 5 | """""""""" 6 | 7 | let s:starting_up_msg = 'GHCi is still starting up...' 8 | 9 | function! ghci#repl#eval(...) abort 10 | if !g:ghci_started 11 | echoerr s:starting_up_msg 12 | else 13 | " Given no arguments, this requests an expression from the user and 14 | " evaluates it in the GHCi REPL. 15 | if a:0 == 0 16 | call inputsave() 17 | let l:eval = input('Command: ') 18 | call inputrestore() 19 | elseif a:0 == 1 20 | let l:eval = a:1 21 | else 22 | echomsg 'Call with nothing for eval or with command string.' 23 | return 24 | endif 25 | 26 | let g:ghci_echo_next = 1 27 | call ghci#repl#send(l:eval) 28 | endif 29 | endfunction 30 | 31 | function! ghci#repl#load_current_module() abort 32 | if !g:ghci_started 33 | echoerr s:starting_up_msg 34 | else 35 | " Loads the current module, inferred from the given filename. 36 | call ghci#repl#send(':l ' . ghci#loc#detect_module()) 37 | endif 38 | endfunction 39 | 40 | function! ghci#repl#load_current_file() abort 41 | if !g:ghci_started 42 | echoerr s:starting_up_msg 43 | else 44 | " Load the current file 45 | call ghci#repl#send(':l ' . expand('%:p')) 46 | endif 47 | endfunction 48 | 49 | " the `visual` argument should be either '' or 'V' 50 | function! ghci#repl#type(visual) abort 51 | if (a:visual == 'V') 52 | let l:expr = ghci#util#get_visual_selection() 53 | else 54 | let l:expr = ghci#util#get_haskell_identifier() 55 | endif 56 | 57 | call ghci#repl#eval(':type ' . l:expr) 58 | endfunction 59 | 60 | function! ghci#repl#info() abort 61 | if !g:ghci_started 62 | echoerr s:starting_up_msg 63 | else 64 | let l:ident = ghci#util#get_haskell_identifier() 65 | call ghci#repl#eval(':info ' . l:ident) 66 | endif 67 | endfunction 68 | 69 | function! ghci#repl#send(str) abort 70 | " Sends a:str to the GHCi REPL. 71 | if !exists('g:ghci_buffer_id') 72 | echomsg 'GHCi not running.' 73 | return 74 | endif 75 | call jobsend(g:ghci_job_id, add([a:str], '')) 76 | endfunction 77 | 78 | function! ghci#repl#reload() abort 79 | if !g:ghci_started 80 | echoerr s:starting_up_msg 81 | else 82 | " Truncate file, so that we don't show stale results while recompiling 83 | call ghci#maker#write_update([]) 84 | 85 | call ghci#repl#send(':reload') 86 | endif 87 | endfunction 88 | 89 | """""""""" 90 | " Private: 91 | """""""""" 92 | 93 | function! s:paste_type(lines) abort 94 | let l:message = join(a:lines, '\n') 95 | if l:message =~# ' :: ' 96 | call append(line('.')-1, a:lines) 97 | else 98 | echomsg l:message 99 | end 100 | endfunction 101 | 102 | -------------------------------------------------------------------------------- /autoload/ghci/util.vim: -------------------------------------------------------------------------------- 1 | """""""""" 2 | " Util: 3 | " 4 | " This file contains functions that are useful for multiple modules, but that 5 | " don't fit specifically in any one. 6 | """"""""" 7 | 8 | function! ghci#util#get_ghci_window() abort 9 | " Returns the window ID that the GHCi process is on, or -1 if it isn't 10 | " found. 11 | return bufwinnr('GHCi') 12 | endfunction 13 | 14 | function! ghci#util#make_command(cmd) abort 15 | let l:info = ghci#loc#get_identifier_information() 16 | return join([a:cmd, l:info.module, l:info.line, l:info.beg_col, l:info.line, l:info.end_col, l:info.identifier], ' ') 17 | endfunction 18 | 19 | """""""""" 20 | " The following functions were copied from ghcmod-vim. 21 | """""""""" 22 | " 23 | " Return the current haskell identifier 24 | function! ghci#util#get_haskell_identifier() abort 25 | let l:c = col ('.') - 1 26 | let l:l = line('.') 27 | let l:ll = getline(l:l) 28 | let l:ll1 = strpart(l:ll, 0, l:c) 29 | let l:ll1 = matchstr(l:ll1, "[a-zA-Z0-9_'.]*$") 30 | let l:ll2 = strpart(l:ll, l:c, strlen(l:ll) - l:c + 1) 31 | let l:ll2 = matchstr(l:ll2, "^[a-zA-Z0-9_'.]*") 32 | return l:ll1 . l:ll2 33 | endfunction "}}} 34 | 35 | function! ghci#util#print_warning(msg) abort "{{{ 36 | echohl WarningMsg 37 | echomsg a:msg 38 | echohl None 39 | endfunction "}}} 40 | 41 | function! ghci#util#print_error(msg) abort "{{{ 42 | echohl ErrorMsg 43 | echomsg a:msg 44 | echohl None 45 | endfunction "}}} 46 | 47 | function! ghci#util#getcol(line, col) abort "{{{ 48 | let l:str = getline(a:line)[:(a:col - 1)] 49 | let l:tabcnt = len(substitute(l:str, '[^\t]', '', 'g')) 50 | return a:col + 7 * l:tabcnt 51 | endfunction "}}} 52 | 53 | function! ghci#util#tocol(line, col) abort "{{{ 54 | let l:str = getline(a:line) 55 | let l:len = len(l:str) 56 | let l:col = 0 57 | for l:i in range(1, l:len) 58 | let l:col += (l:str[l:i - 1] ==# "\t" ? 8 : 1) 59 | if l:col >= a:col 60 | return l:i 61 | endif 62 | endfor 63 | return l:len + 1 64 | endfunction "}}} 65 | 66 | " https://stackoverflow.com/questions/1533565/how-to-get-visually-selected-text-in-vimscript 67 | function! ghci#util#get_visual_selection() abort 68 | " Why is this not a built-in Vim script function?! 69 | let [line_start, column_start] = getpos("'<")[1:2] 70 | let [line_end, column_end] = getpos("'>")[1:2] 71 | let lines = getline(line_start, line_end) 72 | if len(lines) == 0 73 | return '' 74 | endif 75 | let lines[-1] = lines[-1][: column_end - (&selection == 'inclusive' ? 1 : 2)] 76 | let lines[0] = lines[0][column_start - 1:] 77 | return join(lines, "\n") 78 | endfunction 79 | 80 | 81 | " vim: set ts=4 sw=4 et fdm=marker: 82 | -------------------------------------------------------------------------------- /doc/ghci.txt: -------------------------------------------------------------------------------- 1 | *ghci.txt* Interactive program development for Haskell 2 | *ghci* 3 | 4 | 5 | mm m mmmmmm mmmm m m mmmmm m m mmm m m mmm mmmmm 6 | #"m # # m" "m "m m" # ## ## m" " # # m" " # 7 | # #m # #mmmmm # # # # # # ## # # mm #mmmm# # # 8 | # # # # # # "mm" # # "" # """ # # # # # # 9 | # ## #mmmmm #mm# ## mm#mm # # "mmm" # # "mmm" mm#mm 10 | 11 | 12 | 13 | ============================================================================= 14 | CONTENTS *ghci-contents* 15 | 16 | 1. FEATURES .............................. |ghci-features| 17 | 2. OVERVIEW .............................. |ghci-overview| 18 | 3. USAGE ................................. |ghci-usage| 19 | 3.1 The GHCi Package ................... |ghci-package| 20 | 3.2 GHCi Background Process ............ |ghci-backend| 21 | 3.3 The GHCi REPL Buffer ............... |ghci-repl| 22 | 3.4 Loading Code ....................... |ghci-load| 23 | 4. CONFIGURATION ......................... |ghci-config| 24 | 5. CAVEATS ............................... |ghci-caveats| 25 | 6. LICENSE ............................... |ghci-license| 26 | 7. CREDITS ............................... |ghci-credits| 27 | 28 | ============================================================================= 29 | FEATURES *ghci-features* 30 | 31 | On-the-fly Typechecking~ 32 | 33 | This plugin reports errors and warnings as you work on your file using the 34 | Neomake plugin. Errors appear asynchronously, and don't block the UI. 35 | 36 | Built-in REPL~ 37 | 38 | Work with your Haskell code directly in GHCi using Neovim |:terminal| buffers. 39 | Load your file and play around with top-level functions directly. 40 | 41 | ============================================================================= 42 | OVERVIEW *ghci-overview* 43 | 44 | To open the REPL: |:GhciOpen| 45 | To load into the REPL: |:GhciLoadCurrentFile| 46 | To reload whatever's in the REPL: |:GhciReload| 47 | To evaluate an expression from outside the REPL: |:GhciEvaluate| 48 | 49 | ============================================================================= 50 | USAGE *ghci-usage* 51 | 52 | GHCi Background Process~ 53 | *ghci-process* 54 | `neovim-ghci` maintains a GHCi process running in the background. This 55 | works using Neovim's asynchronous job control API (|job-control|). 56 | 57 | By default, the GHCi process is started automatically when you open a Haskell 58 | buffer. Some people might want to control when (and whether) the background 59 | process is started. See |g:ghci_start_immediately|. 60 | 61 | 62 | *:GhciStart* 63 | *:GhciKill* 64 | *:GhciRestart* 65 | :GhciStart Commands for starting and stopping the background 66 | :GhciKill process. You shouldn't need |:GhciStart| unless you 67 | :GhciRestart have unset |g:ghci_start_immediately|. 68 | 69 | These commands only manipulate the background process. 70 | To manipulate the GHCi buffer, see |ghci-buffer|. 71 | 72 | 73 | The GHCi Buffer~ 74 | *ghci-buffer* 75 | Normally, GHCi runs in the background. However, you can bring it to the 76 | foreground into a |:terminal| buffer. There, you can directly interact with 77 | the REPL. 78 | 79 | *:GhciOpen* 80 | *:GhciHide* 81 | :GhciOpen |:GhciOpen| makes sure that the background process is started 82 | :GhciHide (|ghci-process|), then shows the REPL in a split. 83 | 84 | |:GhciHide| hides the open REPL buffer. It remains running 85 | in the background. To kill the background process, see 86 | |:GhciKill|. 87 | 88 | By default, the REPL opens in a horizontal split. To instead 89 | use a vertical split use |CTRL-W_H| or |CTRL-W_L|. To move 90 | it to it's own tab: |CTRL-W_T|. 91 | 92 | *:GhciUses* 93 | :GhciEval [cmd] Runs a command in the GHCi process, and displays the 94 | result. Useful when you don't want to have to open up the 95 | full REPL. 96 | 97 | Calling |:GhciEval| will 0 arguments will prompt you to 98 | enter a command. 99 | 100 | 101 | Loading Code~ 102 | *ghci-load* 103 | It's convenient to be able to load your file's or module's top-level bindings 104 | into the GHCi REPL so that you can play around. Additionally, some commands 105 | won't work until you've loaded the current file or module. 106 | 107 | *:GhciReload* 108 | :GhciReload Issues a `:reload` to GHCi. This rebuilds your code, and 109 | you'll be able to see type checking and compilation errors. 110 | If you have Neomake installed, the errors will show up in 111 | the sign column and loclist. 112 | 113 | *:GhciLoadCurrentFile* 114 | :GhciLoadCurrentFile 115 | Gets the current file and loads it into GHCi (using 116 | `:load`). 117 | 118 | *:GhciLoadCurrentModule* 119 | :GhciLoadCurrentModule 120 | `neovim-ghci` tries to parse the module you're working on 121 | right now, then loads it into GHCi (using `:load`). 122 | 123 | Type and Info~ 124 | 125 | *:GhciType* 126 | :GhciType Issues a `:type` to GHCi, with the identifier under point, or the 127 | visual selection, as an argument. 128 | 129 | *:GhciInfo* 130 | :GhciInfo Issues an `:info` to GHCi, with the identifier under point as 131 | an argument. 132 | 133 | ============================================================================= 134 | CONFIGURATION *ghci-config* 135 | 136 | *g:ghci_prompt_regex* 137 | 138 | Default: `'[^-]>'` 139 | 140 | If you use a custom GHCi prompt, you may need to modify the prompt regex so 141 | that it matches your custom prompt. 142 | 143 | *g:ghci_start_immediately* 144 | 145 | Default: `1` 146 | 147 | GHCi initializes and starts immediately by default. To prevent this from 148 | happening manually, set this to `0`. 149 | 150 | *g:ghci_use_neomake* 151 | 152 | Default: `1` 153 | 154 | This plugin attempts to use Neomake (|neomake.txt|) if it is installed. To 155 | opt out of using Neomake (including silencing warnings about Neomake), set 156 | this to `0`. 157 | 158 | *g:ghci_command* 159 | 160 | Default: 'ghci' 161 | 162 | How to start the GHCi process. Other values can be `cabal repl`, or `cabal 163 | new-repl`. 164 | 165 | *g:ghci_command_line_options* 166 | 167 | Default: '' 168 | 169 | Options that configure the behaviour of GHCi, e.g. `-fobject-code`. 170 | 171 | ============================================================================= 172 | CAVEATS *ghci-caveats* 173 | 174 | - Running `:Neomake!` directly will not work. You need to run |:GhciReload| 175 | instead. 176 | 177 | - Some commands may have unexpected side-effects if you have an autocommand 178 | that automatically switches to insert mode when entering a terminal buffer. 179 | 180 | ============================================================================= 181 | LICENSE *ghci-license* 182 | 183 | BSD3 License (the same license as `ghcmod-vim`). 184 | 185 | ============================================================================= 186 | CREDITS *ghci-credits* 187 | 188 | `neovim-ghci` is adapted, by @owickstrom, from 189 | https://github.com/parsonsmatt/neovim-intero, which is written by 190 | @parsonsmatt, with significant contributions from @rdnetto. 191 | 192 | `neovim-ghci` welcomes new contributions! Submit pull requests and open 193 | issues on GitHub: https://github.com/owickstrom/neovim-ghci 194 | 195 | 196 | vim:tw=78:et:ts=2:ft=help:norl: 197 | -------------------------------------------------------------------------------- /ghci.py: -------------------------------------------------------------------------------- 1 | '''Functions consumed from Vimscript. 2 | 3 | This file is loaded as a module, to avoid polluting the global namespace. 4 | ''' 5 | 6 | import os.path 7 | import re 8 | import vim 9 | 10 | 11 | # Regexes used to remove control characters 12 | regexes = [ 13 | # Filter out ANSI codes - they are needed for interactive use, but we don't care about them. 14 | # Regex from: https://stackoverflow.com/questions/14693701/how-can-i-remove-the-ansi-escape-sequences-from-a-string-in-python 15 | # Note that we replace [0-?] with [0-Z] to filter out the arrow keys as well (xterm codes) 16 | re.compile(r"(\x9B|\x1B\[)[0-Z]*[ -\/]*[@-~]"), 17 | # Filter out DECPAM/DECPNM, since they're emitted as well 18 | # Source: https://www.xfree86.org/4.8.0/ctlseqs.html 19 | re.compile(r"\x1B[>=]") 20 | ] 21 | 22 | 23 | def strip_control_chars(var): 24 | '''Removes control characters from the specified variable.''' 25 | 26 | return strip_internal(vim.eval(var)) 27 | 28 | 29 | def strip_internal(s): 30 | '''Helper function for removing control characters.''' 31 | 32 | for r in regexes: 33 | s = r.sub("", s) 34 | 35 | return s 36 | 37 | -------------------------------------------------------------------------------- /plugin/ghci.vim: -------------------------------------------------------------------------------- 1 | if exists('g:did_plugin_ghci') && g:did_plugin_ghci 2 | finish 3 | endif 4 | let g:did_plugin_ghci = 1 5 | 6 | if !exists('g:ghci_use_neomake') 7 | let g:ghci_use_neomake = 1 8 | endif 9 | 10 | " Starts the GHCi process in the background. 11 | command! -nargs=0 -bang GhciStart call ghci#process#start() 12 | " Kills the GHCi process. 13 | command! -nargs=0 -bang GhciKill call ghci#process#kill() 14 | " Opens the ghci buffer. 15 | command! -nargs=0 -bang GhciOpen call ghci#process#open() 16 | " Hides the ghci buffer. 17 | command! -nargs=0 -bang GhciHide call ghci#process#hide() 18 | " Loads the current module in ghci. 19 | command! -nargs=0 -bang GhciLoadCurrentModule call ghci#repl#load_current_module() 20 | " Loads the current file in ghci. 21 | command! -nargs=0 -bang GhciLoadCurrentFile call ghci#repl#load_current_file() 22 | " Prompts user for a string to eval 23 | command! -nargs=? -bang GhciEval call ghci#repl#eval() 24 | " Gets the type at the current point or in visual selection 25 | command! -nargs=0 -bang -range GhciType call ghci#repl#type(visualmode()) 26 | " Gets info for the identifier at the current point 27 | command! -nargs=0 -bang GhciInfo call ghci#repl#info() 28 | " Reload 29 | command! -nargs=0 -bang GhciReload call ghci#repl#reload() 30 | " Kill and restart the GHCi process 31 | command! -nargs=0 -bang GhciRestart call ghci#process#restart() 32 | 33 | if g:ghci_use_neomake 34 | " Neomake integration 35 | 36 | " Try GHC 8 errors and warnings, then GHC 7 errors and warnings, and regard 37 | " lines starting with two spaces as continuations on an error message. All 38 | " other lines are disregarded. This gives a clean one-line-per-entry in the 39 | " QuickFix list. 40 | let s:efm = '%E%f:%l:%c:\ error:%#,' . 41 | \ '%W%f:%l:%c:\ warning:%#,' . 42 | \ '%W%f:%l:%c:\ warning:\ [-W%.%#]%#,' . 43 | \ '%f:%l:%c:\ %trror: %m,' . 44 | \ '%f:%l:%c:\ %tarning: %m,' . 45 | \ '%E%f:%l:%c:%#,' . 46 | \ '%E%f:%l:%c:%m,' . 47 | \ '%W%f:%l:%c:\ Warning:%#,' . 48 | \ '%C\ \ %m%#,' . 49 | \ '%-G%.%#' 50 | 51 | let g:neomake_ghci_maker = { 52 | \ 'exe': 'cat', 53 | \ 'args': [ghci#maker#get_log_file()], 54 | \ 'errorformat': s:efm 55 | \ } 56 | endif 57 | 58 | " Store the path to the plugin directory, so we can lazily load the Python module 59 | let g:ghci_plugin_root = expand(':p:h:h') 60 | 61 | " vim: set ts=4 sw=4 et fdm=marker: 62 | --------------------------------------------------------------------------------