├── .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 | [](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 |
--------------------------------------------------------------------------------