├── after └── ftplugin │ ├── lhaskell │ └── ghcmod.vim │ └── haskell │ └── ghcmod.vim ├── test ├── data │ ├── detect_module │ │ ├── Normal.hs │ │ ├── NoModuleDecl.hs │ │ ├── Foo.hs │ │ ├── ModuleWithSpace.hs │ │ └── ModuleDeclInComment.hs │ ├── without-cabal │ │ ├── Foo │ │ │ └── Bar.hs │ │ └── Main.hs │ ├── with-cabal │ │ ├── Setup.hs │ │ ├── src │ │ │ ├── Foo.hs │ │ │ └── Foo │ │ │ │ └── Bar.hs │ │ └── with-cabal.cabal │ ├── with whitespace │ │ ├── Setup.hs │ │ ├── src │ │ │ ├── Foo.hs │ │ │ └── Foo │ │ │ │ └── Bar.hs │ │ └── with-cabal.cabal │ ├── split │ │ └── Split.hs │ ├── failure │ │ ├── Main.hs │ │ └── SyntaxError.hs │ ├── sig │ │ ├── SigFunction.hs │ │ └── SigInstance.hs │ ├── th │ │ ├── Hoge.hs │ │ └── Fuga.hs │ └── th with whitespace │ │ ├── Hoge.hs │ │ └── Fuga.hs ├── test_split.vim ├── before.vim ├── test_command_split.vim ├── test_info.vim ├── test_type.vim ├── test_command_sig_codegen.vim ├── test_detect_module.vim ├── test_basedir.vim ├── test_command_check.vim ├── test_build_command.vim ├── test_expand.vim ├── test_command_type.vim ├── test_check.vim └── test_lint.vim ├── .gitignore ├── plugin └── ghcmod.vim ├── dist.sh ├── dist.vim ├── .travis.yml ├── install-deps.sh ├── test.sh ├── CONTRIBUTING.md ├── autoload ├── ghcmod │ ├── async.vim │ ├── diagnostics.vim │ ├── type.vim │ ├── util.vim │ └── command.vim └── ghcmod.vim ├── ChangeLog.md ├── README.md └── doc └── ghcmod.txt /after/ftplugin/lhaskell/ghcmod.vim: -------------------------------------------------------------------------------- 1 | ../haskell/ghcmod.vim -------------------------------------------------------------------------------- /test/data/detect_module/Normal.hs: -------------------------------------------------------------------------------- 1 | module Normal where 2 | -------------------------------------------------------------------------------- /test/data/without-cabal/Foo/Bar.hs: -------------------------------------------------------------------------------- 1 | module Foo.Bar where 2 | -------------------------------------------------------------------------------- /test/data/detect_module/NoModuleDecl.hs: -------------------------------------------------------------------------------- 1 | main :: IO () 2 | main = return () 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | vimproc 3 | tinytest 4 | verbose.log 5 | stdout.log 6 | -------------------------------------------------------------------------------- /test/data/with-cabal/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /test/data/with whitespace/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /plugin/ghcmod.vim: -------------------------------------------------------------------------------- 1 | command! -nargs=0 GhcModDiagnostics call ghcmod#diagnostics#report() 2 | -------------------------------------------------------------------------------- /test/data/detect_module/Foo.hs: -------------------------------------------------------------------------------- 1 | module Foo (foo) where 2 | 3 | foo :: Int 4 | foo = 42 5 | -------------------------------------------------------------------------------- /test/data/split/Split.hs: -------------------------------------------------------------------------------- 1 | module Split (f) where 2 | 3 | f :: [a] -> a 4 | f x = undefined 5 | -------------------------------------------------------------------------------- /test/data/with-cabal/src/Foo.hs: -------------------------------------------------------------------------------- 1 | module Foo where 2 | import Foo.Bar 3 | 4 | foo = bar ++ "" 5 | -------------------------------------------------------------------------------- /test/data/without-cabal/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | import Foo.Bar 3 | 4 | main = return () 5 | -------------------------------------------------------------------------------- /test/data/with whitespace/src/Foo.hs: -------------------------------------------------------------------------------- 1 | module Foo where 2 | import Foo.Bar 3 | 4 | foo = bar ++ "" 5 | -------------------------------------------------------------------------------- /test/data/failure/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | compileError = "foo" + 0 4 | 5 | main = return () 6 | -------------------------------------------------------------------------------- /test/data/failure/SyntaxError.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | compileError = 4 | 5 | main = return () 6 | -------------------------------------------------------------------------------- /test/data/sig/SigFunction.hs: -------------------------------------------------------------------------------- 1 | module SigFunction where 2 | 3 | func :: [a] -> Maybe b -> (a -> b) -> (a,b) 4 | -------------------------------------------------------------------------------- /test/data/with-cabal/src/Foo/Bar.hs: -------------------------------------------------------------------------------- 1 | module Foo.Bar (bar) where 2 | 3 | bar = x 4 | where 5 | x = id $ "bar" 6 | -------------------------------------------------------------------------------- /test/data/with whitespace/src/Foo/Bar.hs: -------------------------------------------------------------------------------- 1 | module Foo.Bar (bar) where 2 | 3 | bar = x 4 | where 5 | x = id $ "bar" 6 | -------------------------------------------------------------------------------- /dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec vim -e -N -u NONE --cmd 'set runtimepath=.,~/.vim/bundle/vimproc' -S dist.vim -c quit < /dev/null 3 | -------------------------------------------------------------------------------- /test/data/detect_module/ModuleWithSpace.hs: -------------------------------------------------------------------------------- 1 | module ModuleWithSpace where 2 | 3 | moduleWithSpace :: Char 4 | moduleWithSpace = ' ' 5 | -------------------------------------------------------------------------------- /dist.vim: -------------------------------------------------------------------------------- 1 | let s:version = join(ghcmod#version(), '.') 2 | echo system(printf('git archive --prefix=ghcmod-vim-%s/ -o ghcmod-vim-%s.zip v%s after autoload doc', s:version, s:version, s:version)) 3 | -------------------------------------------------------------------------------- /test/data/sig/SigInstance.hs: -------------------------------------------------------------------------------- 1 | module SigInstance where 2 | 3 | newtype D = D (Int,String) 4 | 5 | class C a where 6 | cInt :: a -> Int 7 | cString :: a -> String 8 | 9 | instance C D where 10 | -------------------------------------------------------------------------------- /test/data/th/Hoge.hs: -------------------------------------------------------------------------------- 1 | module Hoge (hoge) where 2 | 3 | import Language.Haskell.TH 4 | import Language.Haskell.TH.Quote 5 | 6 | hoge :: QuasiQuoter 7 | hoge = QuasiQuoter (litE . stringL) undefined undefined undefined 8 | -------------------------------------------------------------------------------- /test/data/th with whitespace/Hoge.hs: -------------------------------------------------------------------------------- 1 | module Hoge (hoge) where 2 | 3 | import Language.Haskell.TH 4 | import Language.Haskell.TH.Quote 5 | 6 | hoge :: QuasiQuoter 7 | hoge = QuasiQuoter (litE . stringL) undefined undefined undefined 8 | -------------------------------------------------------------------------------- /test/data/th/Fuga.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell, QuasiQuotes #-} 2 | module Fuga (fuga, piyo) where 3 | import Hoge (hoge) 4 | 5 | fuga :: String 6 | fuga = [hoge| 7 | hoge 8 | fuga 9 | piyo 10 | |] 11 | 12 | piyo :: String 13 | piyo = [hoge| hoge fuga piyo |] 14 | -------------------------------------------------------------------------------- /test/data/th with whitespace/Fuga.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell, QuasiQuotes #-} 2 | module Fuga (fuga, piyo) where 3 | import Hoge (hoge) 4 | 5 | fuga :: String 6 | fuga = [hoge| 7 | hoge 8 | fuga 9 | piyo 10 | |] 11 | 12 | piyo :: String 13 | piyo = [hoge| hoge fuga piyo |] 14 | -------------------------------------------------------------------------------- /test/test_split.vim: -------------------------------------------------------------------------------- 1 | let s:unit = tinytest#new() 2 | 3 | function! s:unit.test_split() 4 | edit test/data/split/Split.hs 5 | let l:decls = ghcmod#split(4, 3, expand('%:p')) 6 | call self.assert.equal(['f [] = undefined', 'f (x:xs) = undefined'], l:decls) 7 | endfunction 8 | 9 | call s:unit.run() 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: haskell 2 | ghc: 3 | - 7.4 4 | - 7.6 5 | - 7.8 6 | sudo: false 7 | install: ./install-deps.sh 8 | before_script: vim --version 9 | script: ./test.sh 10 | notifications: 11 | webhooks: 12 | urls: 13 | - https://webhooks.gitter.im/e/cfa02866390ca23ea84c 14 | on_success: always 15 | on_failure: always 16 | on_start: true 17 | -------------------------------------------------------------------------------- /test/data/detect_module/ModuleDeclInComment.hs: -------------------------------------------------------------------------------- 1 | {-| This is a long module level haddock 2 | 3 | Example: 4 | 5 | > module FakeModule where 6 | > 7 | > fake = fake 8 | 9 | > module AnotherFakeModule where 10 | > 11 | > fake = fake 12 | 13 | > module YetAnotherFakeModule where 14 | > 15 | > fake = fake 16 | -} 17 | 18 | module ActualModule where 19 | 20 | actual = actual 21 | -------------------------------------------------------------------------------- /test/data/with-cabal/with-cabal.cabal: -------------------------------------------------------------------------------- 1 | name: with-cabal 2 | version: 0.1.0 3 | synopsis: with-cabal 4 | description: with-cabal 5 | build-type: Simple 6 | maintainer: eagletmt@gmail.com 7 | category: Testing 8 | license: BSD3 9 | cabal-version: >= 1.6 10 | 11 | library 12 | build-depends: base == 4.* 13 | ghc-options: -W -Wall 14 | hs-source-dirs: src 15 | exposed-modules: 16 | Foo 17 | Foo.Bar 18 | -------------------------------------------------------------------------------- /test/data/with whitespace/with-cabal.cabal: -------------------------------------------------------------------------------- 1 | name: with-cabal 2 | version: 0.1.0 3 | synopsis: with-cabal 4 | description: with-cabal 5 | build-type: Simple 6 | maintainer: eagletmt@gmail.com 7 | category: Testing 8 | license: BSD3 9 | cabal-version: >= 1.6 10 | 11 | library 12 | build-depends: base == 4.* 13 | ghc-options: -W -Wall 14 | hs-source-dirs: src 15 | exposed-modules: 16 | Foo 17 | Foo.Bar 18 | -------------------------------------------------------------------------------- /install-deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -ex 3 | 4 | if [ -d tinytest ]; then 5 | (cd tinytest; git pull origin master) 6 | else 7 | git clone https://github.com/eagletmt/tinytest.git 8 | fi 9 | 10 | if [ -d vimproc ]; then 11 | cd vimproc 12 | git pull origin master 13 | else 14 | git clone https://github.com/Shougo/vimproc.git 15 | cd vimproc 16 | fi 17 | make -f make_unix.mak 18 | 19 | cabal update 20 | cabal install happy 21 | cabal install -j ghc-mod 22 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | shopt -s nullglob 4 | 5 | retval=0 6 | for f in test/test_*.vim 7 | do 8 | testname=${f#test/test_} 9 | testname=${testname%.vim} 10 | echo "Running $testname" 11 | rm -f verbose.log 12 | if vim -e -N -u NONE -S test/before.vim -S "$f" < /dev/null; then 13 | cat stdout.log 14 | else 15 | retval=$[retval + 1] 16 | cat stdout.log 17 | cat verbose.log 18 | echo 19 | fi 20 | done 21 | 22 | exit $retval 23 | -------------------------------------------------------------------------------- /test/before.vim: -------------------------------------------------------------------------------- 1 | set verbosefile=verbose.log 2 | 3 | " Remove user's runtime path 4 | set runtimepath-=~/.vim runtimepath-=~/.vim/after 5 | let &runtimepath = join([ 6 | \ &runtimepath, 7 | \ fnamemodify('vimproc', ':p'), 8 | \ fnamemodify('tinytest', ':p'), 9 | \ fnamemodify('.', ':p'), 10 | \ fnamemodify('./after', ':p'), 11 | \ ], ',') 12 | 13 | syntax enable 14 | filetype plugin indent on 15 | 16 | let g:tinytest_default_config = { 'reporter': 'cli' } 17 | -------------------------------------------------------------------------------- /test/test_command_split.vim: -------------------------------------------------------------------------------- 1 | let s:unit = tinytest#new() 2 | 3 | function! s:unit.test_command_split() 4 | edit test/data/split/Split.hs 5 | call cursor(4, 3) 6 | GhcModSplitFunCase 7 | " Don't move cursor line 8 | call self.assert.equal(line('.'), 4) 9 | call self.assert.equal(getline('.'), 'f [] = undefined') 10 | call self.assert.equal(getline(line('.')+1), 'f (x:xs) = undefined') 11 | endfunction 12 | 13 | function! s:unit.teardown() 14 | " Discard changes 15 | bdelete! 16 | endfunction 17 | 18 | call s:unit.run() 19 | -------------------------------------------------------------------------------- /test/test_info.vim: -------------------------------------------------------------------------------- 1 | function! s:info(fexp) 2 | return ghcmod#info(a:fexp, expand('%:p')) 3 | endfunction 4 | 5 | let s:unit = tinytest#new() 6 | 7 | function! s:unit.teardown() 8 | bdelete 9 | endfunction 10 | 11 | function! s:unit.test_info() 12 | edit test/data/with-cabal/src/Foo.hs 13 | call self.assert.match('^bar :: \[Char\]', s:info('bar')) 14 | endfunction 15 | 16 | function! s:unit.test_info_syntax_error() 17 | edit test/data/failure/SyntaxError.hs 18 | call self.assert.match('^Cannot show info', s:info('main')) 19 | endfunction 20 | 21 | call s:unit.run() 22 | -------------------------------------------------------------------------------- /test/test_type.vim: -------------------------------------------------------------------------------- 1 | let s:unit = tinytest#new() 2 | 3 | function! s:unit.teardown() 4 | bdelete 5 | endfunction 6 | 7 | function! s:unit.test_type() 8 | edit test/data/with-cabal/src/Foo.hs 9 | let l:types = ghcmod#type(4, 7, expand('%:p')) 10 | call self.assert.equal([ 11 | \ [[4, 7, 4, 10], '[Char]'], 12 | \ [[4, 7, 4, 16], '[Char]'], 13 | \ [[4, 1, 4, 16], '[Char]'], 14 | \ ], l:types) 15 | endfunction 16 | 17 | function! s:unit.test_type_compilation_failure() 18 | edit test/data/failure/Main.hs 19 | let l:types = ghcmod#type(4, 7, expand('%:p')) 20 | call self.assert.empty(l:types) 21 | endfunction 22 | 23 | call s:unit.run() 24 | -------------------------------------------------------------------------------- /test/test_command_sig_codegen.vim: -------------------------------------------------------------------------------- 1 | let s:unit = tinytest#new() 2 | 3 | function! s:unit.test_command_sig_function() 4 | edit test/data/sig/SigFunction.hs 5 | call cursor(3, 1) 6 | GhcModSigCodegen 7 | call self.assert.match('^func x y f = ', getline(line('.')+1)) 8 | endfunction 9 | 10 | function! s:unit.test_command_sig_instance() 11 | edit test/data/sig/SigInstance.hs 12 | call cursor(9, 1) 13 | GhcModSigCodegen 14 | call self.assert.match('^\s*cInt x = ', getline(line('.')+1)) 15 | call self.assert.match('^\s*cString x = ', getline(line('.')+2)) 16 | endfunction 17 | 18 | function! s:unit.teardown() 19 | " Discard changes 20 | bdelete! 21 | endfunction 22 | 23 | call s:unit.run() 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Issues 4 | When submitting an issue, please include the output by `:GhcModDiagnosis`. 5 | 6 | ```vim 7 | GhcModDiagnosis 8 | ``` 9 | 10 | It shows your environment information possibly related to ghcmod.vim. 11 | 12 | - Current filetype 13 | - ghcmod.vim only works in the buffer with filetype haskell. 14 | - Filetype status 15 | - ghcmod.vim is a ftplugin. See `:help filetype-overview` and `:help filetype-plugins`. 16 | - ghc-mod executable 17 | - ghcmod.vim requires [ghc-mod](https://github.com/kazu-yamamoto/ghc-mod) and it must be placed in your `$PATH`. 18 | - vimproc.vim 19 | - ghcmod.vim requires [vimproc.vim](https://github.com/Shougo/vimproc.vim). 20 | - ghc-mod version 21 | -------------------------------------------------------------------------------- /test/test_detect_module.vim: -------------------------------------------------------------------------------- 1 | let s:unit = tinytest#new() 2 | 3 | function! s:unit.teardown() 4 | bdelete 5 | endfunction 6 | 7 | function! s:unit.test_normal() 8 | edit test/data/detect_module/Normal.hs 9 | call self.assert.equal('Normal', ghcmod#detect_module()) 10 | endfunction 11 | 12 | function! s:unit.test_no_module_decl() 13 | edit test/data/detect_module/NoModuleDecl.hs 14 | call self.assert.equal('Main', ghcmod#detect_module()) 15 | endfunction 16 | 17 | function! s:unit.test_module_decl_in_comment() 18 | edit test/data/detect_module/ModuleDeclInComment.hs 19 | call self.assert.equal('ActualModule', ghcmod#detect_module()) 20 | endfunction 21 | 22 | function! s:unit.test_module_with_space() 23 | edit test/data/detect_module/ModuleWithSpace.hs 24 | call self.assert.equal('ModuleWithSpace', ghcmod#detect_module()) 25 | endfunction 26 | 27 | call s:unit.run() 28 | -------------------------------------------------------------------------------- /autoload/ghcmod/async.vim: -------------------------------------------------------------------------------- 1 | let s:sessions = {} 2 | 3 | function! ghcmod#async#exist_session() 4 | return !empty(s:sessions) 5 | endfunction 6 | 7 | function! ghcmod#async#register(obj) 8 | try 9 | let l:key = reltimestr(reltime()) " this value should be unique 10 | if !exists('s:updatetime') 11 | let s:updatetime = &updatetime 12 | endif 13 | let s:sessions[l:key] = a:obj 14 | set updatetime=100 15 | augroup ghcmod-async 16 | execute 'autocmd CursorHold,CursorHoldI * call s:receive(' . string(l:key) . ')' 17 | augroup END 18 | return 1 19 | catch 20 | if exists('l:key') && has_key(s:sessions, l:key) 21 | call s:finalize(l:key) 22 | endif 23 | call ghcmod#print_error(printf('%s %s', v:throwpoint, v:exception)) 24 | return 0 25 | endtry 26 | endfunction 27 | 28 | function! s:receive(key) 29 | if !has_key(s:sessions, a:key) 30 | return 31 | endif 32 | let l:session = s:sessions[a:key] 33 | let [l:cond, l:status] = ghcmod#util#wait(l:session.proc) 34 | if l:cond ==# 'run' 35 | call feedkeys(mode() ==# 'i' ? "\\" : "\", 'n') 36 | return 37 | endif 38 | 39 | call s:finalize(a:key) 40 | call l:session.on_finish(l:cond, l:status) 41 | endfunction 42 | 43 | function! s:finalize(key) 44 | call remove(s:sessions, a:key) 45 | if empty(s:sessions) 46 | augroup ghcmod-async 47 | autocmd! 48 | augroup END 49 | let &updatetime = s:updatetime 50 | unlet s:updatetime 51 | endif 52 | endfunction 53 | 54 | " vim: set ts=2 sw=2 et fdm=marker: 55 | -------------------------------------------------------------------------------- /test/test_basedir.vim: -------------------------------------------------------------------------------- 1 | function! s:basedir() 2 | " normalize path 3 | return fnamemodify(ghcmod#basedir(), ':.') 4 | endfunction 5 | 6 | let s:unit = tinytest#new() 7 | 8 | function! s:unit.teardown() 9 | bdelete 10 | endfunction 11 | 12 | function! s:unit.test_with_cabal() 13 | edit test/data/with-cabal/src/Foo.hs 14 | call self.assert.equal('test/data/with-cabal', s:basedir()) 15 | endfunction 16 | 17 | function! s:unit.test_with_cabal_subdir() 18 | edit test/data/with-cabal/src/Foo/Bar.hs 19 | call self.assert.equal('test/data/with-cabal', s:basedir()) 20 | endfunction 21 | 22 | function! s:unit.test_without_cabal() 23 | edit test/data/without-cabal/Main.hs 24 | call self.assert.equal('test/data/without-cabal', s:basedir()) 25 | endfunction 26 | 27 | function! s:unit.test_without_cabal_subdir() 28 | edit test/data/without-cabal/Foo/Bar.hs 29 | call self.assert.equal('test/data/without-cabal/Foo', s:basedir()) 30 | endfunction 31 | 32 | function! s:unit.test_with_cabal_opt() 33 | let g:ghcmod_use_basedir = 'test/data' 34 | try 35 | edit test/data/with-cabal/src/Foo/Bar.hs 36 | call self.assert.equal(g:ghcmod_use_basedir, s:basedir()) 37 | finally 38 | unlet g:ghcmod_use_basedir 39 | endtry 40 | endfunction 41 | 42 | function! s:unit.test_without_cabal_opt() 43 | let g:ghcmod_use_basedir = 'test/data' 44 | try 45 | edit test/data/without-cabal/Foo/Bar.hs 46 | call self.assert.equal(g:ghcmod_use_basedir, s:basedir()) 47 | finally 48 | unlet g:ghcmod_use_basedir 49 | endtry 50 | endfunction 51 | 52 | call s:unit.run() 53 | -------------------------------------------------------------------------------- /autoload/ghcmod/diagnostics.vim: -------------------------------------------------------------------------------- 1 | function! ghcmod#diagnostics#report() 2 | echomsg 'Current filetype:' &l:filetype 3 | call s:check_filetype() 4 | 5 | echomsg 'ghcmod.vim version:' join(ghcmod#version(), '.') 6 | 7 | " Note: ghcmod#util#* cannot be used until vimproc.vim's availability is 8 | " checked. 9 | 10 | let l:ghc_mod = executable('ghc-mod') 11 | echomsg 'ghc-mod is executable:' l:ghc_mod 12 | if !l:ghc_mod 13 | echomsg ' Your $PATH:' $PATH 14 | return 15 | endif 16 | 17 | try 18 | echomsg 'vimproc.vim:' vimproc#version() 19 | catch /^Vim\%((\a\+)\)\=:E117/ 20 | echomsg 'vimproc.vim is required but not installed' 21 | return 22 | endtry 23 | 24 | echomsg 'ghc-mod version:' join(ghcmod#util#ghc_mod_version(), '.') 25 | 26 | if &l:filetype == 'haskell' 27 | if !exists('b:did_ftplugin_ghcmod') 28 | call ghcmod#util#print_error("ghcmod.vim's ftplugin isn't loaded. You must copy the `after' directory.") 29 | endif 30 | else 31 | call ghcmod#util#print_warning('Run this command in the buffer opening a Haskell file') 32 | endif 33 | 34 | let l:cmd = ghcmod#build_command(['debug']) 35 | echomsg 'ghc-mod debug command:' join(l:cmd, ' ') 36 | for l:line in split(ghcmod#system(l:cmd), '\n') 37 | echomsg l:line 38 | endfor 39 | endfunction 40 | 41 | function! s:check_filetype() 42 | redir => l:ft 43 | silent filetype 44 | redir END 45 | echomsg l:ft[1 :] 46 | if l:ft !~# 'plugin:ON' 47 | call ghcmod#util#print_error("You didn't enable filetype plugin. I highly recommend putting `filetype plugin indent on` to your vimrc") 48 | endif 49 | endfunction 50 | -------------------------------------------------------------------------------- /test/test_command_check.vim: -------------------------------------------------------------------------------- 1 | let s:unit = tinytest#new() 2 | 3 | function! s:unit.teardown() 4 | unlet! g:ghcmod_open_quickfix_function 5 | cclose 6 | bdelete 7 | endfunction 8 | 9 | function! s:unit.test_command_check() 10 | edit test/data/with-cabal/src/Foo.hs 11 | call self.assert.exist(':GhcModCheck') 12 | GhcModCheck 13 | call self.assert.equal('quickfix', &buftype) 14 | call self.assert.equal(2, len(getqflist())) 15 | endfunction 16 | 17 | function! s:unit.test_command_check_no_error() 18 | edit test/data/without-cabal/Foo/Bar.hs 19 | call self.assert.exist(':GhcModCheck') 20 | let l:bufnr = bufnr('%') 21 | GhcModCheck 22 | call self.assert.not_equal('quickfix', &buftype) 23 | call self.assert.equal(l:bufnr, bufnr('%')) 24 | endfunction 25 | 26 | function! s:unit.test_quickfix_function() 27 | let g:ghcmod_open_quickfix_function = 'TestQuickfixFunction' 28 | edit test/data/with-cabal/src/Foo.hs 29 | let l:bufnr = bufnr('%') 30 | let s:qf_list = [] 31 | GhcModCheck 32 | call self.assert.equal(l:bufnr, bufnr('%')) 33 | call self.assert.equal(getqflist(), s:qf_list) 34 | call self.assert.equal(2, len(s:qf_list)) 35 | endfunction 36 | 37 | function! s:unit.test_quickfix_function_no_error() 38 | let g:ghcmod_open_quickfix_function = 'TestQuickfixFunction' 39 | edit test/data/without-cabal/Foo/Bar.hs 40 | let l:bufnr = bufnr('%') 41 | let s:qf_list = ['garbage'] 42 | GhcModCheck 43 | call self.assert.equal(l:bufnr, bufnr('%')) 44 | call self.assert.equal(getqflist(), s:qf_list) 45 | call self.assert.empty(s:qf_list) 46 | endfunction 47 | 48 | function! TestQuickfixFunction() 49 | let s:qf_list = getqflist() 50 | endfunction 51 | 52 | call s:unit.run() 53 | -------------------------------------------------------------------------------- /test/test_build_command.vim: -------------------------------------------------------------------------------- 1 | function! s:build() 2 | return ghcmod#build_command(['do']) 3 | endfunction 4 | 5 | let s:unit = tinytest#new() 6 | 7 | function! s:unit.teardown() 8 | bdelete 9 | endfunction 10 | 11 | function! s:unit.test_build() 12 | edit test/data/without-cabal/Foo/Bar.hs 13 | call self.assert.equal(['ghc-mod', 'do'], s:build()) 14 | endfunction 15 | 16 | function! s:unit.test_build_with_dist_dir() 17 | try 18 | call system('cd test/data/with-cabal; cabal configure; cabal build') 19 | edit test/data/with-cabal/src/Foo/Bar.hs 20 | call self.assert.equal(['ghc-mod', 21 | \ '-g', '-i' . fnamemodify('test/data/with-cabal/dist/build/autogen', ':p:h'), 22 | \ '-g', '-I' . fnamemodify('test/data/with-cabal/dist/build/autogen', ':p:h'), 23 | \ '-g', '-optP-include', 24 | \ '-g', '-optP' . fnamemodify('test/data/with-cabal/dist/build/autogen/cabal_macros.h', ':p'), 25 | \ '-g', '-i' . fnamemodify('test/data/with-cabal/dist/build', ':p:h'), 26 | \ '-g', '-I' . fnamemodify('test/data/with-cabal/dist/build', ':p:h'), 27 | \ 'do'], s:build()) 28 | finally 29 | call system('cd test/data/with-cabal; rm -rf dist') 30 | endtry 31 | endfunction 32 | 33 | function! s:unit.test_build_global_opt() 34 | let g:ghcmod_ghc_options = ['-Wall'] 35 | try 36 | edit test/data/without-cabal/Main.hs 37 | call self.assert.equal(['ghc-mod', '-g', '-Wall', 'do'], s:build()) 38 | finally 39 | unlet g:ghcmod_ghc_options 40 | endtry 41 | endfunction 42 | 43 | function! s:unit.test_build_buffer_opt() 44 | " If b:ghcmod_ghc_options is set, g:ghcmod_ghc_options is ignored 45 | edit test/data/without-cabal/Foo/Bar.hs 46 | let g:ghcmod_ghc_options = ['-Wall'] 47 | try 48 | let b:ghcmod_ghc_options = ['-W'] 49 | call self.assert.equal(['ghc-mod', '-g', '-W', 'do'], s:build()) 50 | finally 51 | unlet g:ghcmod_ghc_options 52 | endtry 53 | endfunction 54 | 55 | call s:unit.run() 56 | -------------------------------------------------------------------------------- /autoload/ghcmod/type.vim: -------------------------------------------------------------------------------- 1 | function! ghcmod#type#new(types, group) "{{{ 2 | let l:obj = deepcopy(s:ghcmod_type) 3 | let l:obj.types = a:types 4 | let l:obj.group = a:group 5 | 6 | augroup ghcmod-type-highlight 7 | autocmd! * 8 | autocmd BufEnter,WinEnter call s:on_enter() 9 | autocmd BufLeave,WinLeave call s:on_leave() 10 | augroup END 11 | 12 | return l:obj 13 | endfunction "}}} 14 | 15 | function! s:on_enter() "{{{ 16 | if exists('b:ghcmod_type') 17 | call b:ghcmod_type.highlight() 18 | endif 19 | endfunction "}}} 20 | 21 | function! s:on_leave() "{{{ 22 | if exists('b:ghcmod_type') 23 | call b:ghcmod_type.clear_highlight() 24 | endif 25 | endfunction "}}} 26 | 27 | let s:ghcmod_type = { 28 | \ 'ix': 0, 29 | \ 'types': [], 30 | \ 'match_id': -1, 31 | \ } 32 | 33 | function! s:ghcmod_type.spans(line, col) "{{{ 34 | if empty(self.types) 35 | return 0 36 | endif 37 | let [l:line1, l:col1, l:line2, l:col2] = self.types[self.ix][0] 38 | return l:line1 <= a:line && a:line <= l:line2 && l:col1 <= a:col && a:col <= l:col2 39 | endfunction "}}} 40 | 41 | function! s:ghcmod_type.type() "{{{ 42 | return self.types[self.ix][1] 43 | endfunction "}}} 44 | 45 | function! s:ghcmod_type.incr_ix() "{{{ 46 | let self.ix = (self.ix + 1) % len(self.types) 47 | endfunction "}}} 48 | 49 | function! s:ghcmod_type.highlight() "{{{ 50 | if empty(self.types) 51 | return 52 | endif 53 | call self.clear_highlight() 54 | let [l:line1, l:col1, l:line2, l:col2] = self.types[self.ix][0] 55 | let l:col1 = ghcmod#util#tocol(l:line1, l:col1) 56 | let l:col2 = ghcmod#util#tocol(l:line2, l:col2) 57 | let self.match_id = matchadd(self.group, '\%' . l:line1 . 'l\%' . l:col1 . 'c\_.*\%' . l:line2 . 'l\%' . l:col2 . 'c') 58 | endfunction "}}} 59 | 60 | function! s:ghcmod_type.clear_highlight() "{{{ 61 | if self.match_id != -1 62 | call matchdelete(self.match_id) 63 | let self.match_id = -1 64 | endif 65 | endfunction "}}} 66 | 67 | " vim: set ts=2 sw=2 et fdm=marker: 68 | -------------------------------------------------------------------------------- /test/test_expand.vim: -------------------------------------------------------------------------------- 1 | function! s:normalize(qflist) 2 | for l:qf in a:qflist 3 | if has_key(l:qf, 'filename') 4 | let l:qf.filename = fnamemodify(l:qf.filename, ':.') 5 | endif 6 | endfor 7 | return a:qflist 8 | endfunction 9 | 10 | function! s:make_qf_pred(qf) 11 | let l:pred = { 'qf': a:qf } 12 | function! l:pred.call(qf) 13 | for l:key in keys(self.qf) 14 | if !has_key(a:qf, l:key) || self.qf[l:key] != a:qf[l:key] 15 | return 0 16 | endif 17 | endfor 18 | return 1 19 | endfunction 20 | return l:pred 21 | endfunction 22 | 23 | let s:unit = tinytest#new() 24 | 25 | function! s:unit.teardown() 26 | bdelete 27 | endfunction 28 | 29 | function! s:unit.test_expand() 30 | edit test/data/th/Fuga.hs 31 | call self.assert.exist(':GhcModExpand') 32 | let l:qflist = ghcmod#expand(expand('%:p')) 33 | call s:normalize(l:qflist) 34 | let l:end_message = 'Splicing end here' 35 | let l:filename = 'test/data/th/Fuga.hs' 36 | call self.assert.any(s:make_qf_pred({ 'lnum': 6, 'col': 8, 'filename': l:filename }), l:qflist) 37 | call self.assert.any(s:make_qf_pred({ 'lnum': 10, 'col': 2, 'filename': l:filename, 'text': l:end_message }), l:qflist) 38 | call self.assert.any(s:make_qf_pred({ 'lnum': 13, 'col': 8, 'filename': l:filename }), l:qflist) 39 | call self.assert.any(s:make_qf_pred({ 'lnum': 13, 'col': 31, 'filename': l:filename, 'text': l:end_message }), l:qflist) 40 | endfunction 41 | 42 | function! s:unit.test_expand_whitespace() 43 | edit test/data/th\ with\ whitespace/Fuga.hs 44 | call self.assert.exist(':GhcModExpand') 45 | let l:qflist = ghcmod#expand(expand('%:p')) 46 | call s:normalize(l:qflist) 47 | let l:end_message = 'Splicing end here' 48 | let l:filename = 'test/data/th with whitespace/Fuga.hs' 49 | call self.assert.any(s:make_qf_pred({ 'lnum': 6, 'col': 8, 'filename': l:filename }), l:qflist) 50 | call self.assert.any(s:make_qf_pred({ 'lnum': 10, 'col': 2, 'filename': l:filename, 'text': l:end_message }), l:qflist) 51 | call self.assert.any(s:make_qf_pred({ 'lnum': 13, 'col': 8, 'filename': l:filename }), l:qflist) 52 | call self.assert.any(s:make_qf_pred({ 'lnum': 13, 'col': 31, 'filename': l:filename, 'text': l:end_message }), l:qflist) 53 | endfunction 54 | 55 | call s:unit.run() 56 | -------------------------------------------------------------------------------- /test/test_command_type.vim: -------------------------------------------------------------------------------- 1 | function! s:capture(cmd) 2 | let l:result = '' 3 | redir => l:result 4 | silent execute a:cmd 5 | redir END 6 | return split(l:result, '\n') 7 | endfunction 8 | 9 | function! s:match_group(match_id) 10 | for l:item in getmatches() 11 | if l:item.id == a:match_id 12 | return l:item.group 13 | endif 14 | endfor 15 | return '' 16 | endfunction 17 | 18 | function! s:type() 19 | let [l:type] = s:capture('GhcModType') 20 | return [l:type, s:match_group(b:ghcmod_type.match_id)] 21 | endfunction 22 | 23 | let s:unit = tinytest#new() 24 | 25 | function! s:unit.teardown() 26 | bdelete 27 | endfunction 28 | 29 | function! s:unit.test_command_type() 30 | edit test/data/with-cabal/src/Foo.hs 31 | call cursor(4, 7) 32 | let [l:type, l:group] = s:type() 33 | call self.assert.equal(l:type, '[Char]') 34 | call self.assert.equal(l:group, 'Search') 35 | endfunction 36 | 37 | function! s:unit.test_command_type_toggle() 38 | edit test/data/with-cabal/src/Foo.hs 39 | call cursor(4, 11) 40 | let [l:type, _] = s:type() 41 | call self.assert.equal('[Char] -> [Char] -> [Char]', l:type) 42 | let [l:type, _] = s:type() 43 | call self.assert.equal('[Char]', l:type) 44 | endfunction 45 | 46 | function! s:unit.test_command_type_highlight() 47 | let g:ghcmod_type_highlight = 'WildMenu' 48 | try 49 | edit test/data/with-cabal/src/Foo.hs 50 | call cursor(4, 7) 51 | let [_, l:group] = s:type() 52 | call self.assert.equal('WildMenu', l:group) 53 | finally 54 | unlet g:ghcmod_type_highlight 55 | endtry 56 | endfunction 57 | 58 | function! s:unit.test_command_type_match_change() 59 | new test/data/with-cabal/src/Foo.hs 60 | call cursor(4, 7) 61 | silent GhcModType 62 | let l:prev_id = b:ghcmod_type.match_id 63 | let l:prev_group = s:match_group(l:prev_id) 64 | let l:bufnr = bufnr('%') 65 | wincmd p 66 | let l:id = getbufvar(l:bufnr, 'ghcmod_type').match_id 67 | let l:group = s:match_group(l:prev_id) 68 | wincmd p 69 | 70 | call self.assert.equal('Search', l:prev_group) 71 | call self.assert.equal('', l:group) 72 | call self.assert.not_equal(-1, l:prev_id) 73 | call self.assert.equal(-1, l:id) 74 | endfunction 75 | 76 | function! s:unit.test_command_type_clear() 77 | edit test/data/with-cabal/src/Foo.hs 78 | let l:prev_matches = getmatches() 79 | call cursor(4, 7) 80 | silent GhcModType 81 | call self.assert.exist('b:ghcmod_type') 82 | GhcModTypeClear 83 | call self.assert.not_exist('b:ghcmod_type') 84 | let l:matches = getmatches() 85 | call self.assert.equal(l:prev_matches, l:matches) 86 | endfunction 87 | 88 | call s:unit.run() 89 | -------------------------------------------------------------------------------- /after/ftplugin/haskell/ghcmod.vim: -------------------------------------------------------------------------------- 1 | if exists('b:did_ftplugin_ghcmod') && b:did_ftplugin_ghcmod 2 | finish 3 | endif 4 | let b:did_ftplugin_ghcmod = 1 5 | 6 | if !exists('s:has_vimproc') 7 | try 8 | call vimproc#version() 9 | let s:has_vimproc = 1 10 | catch /^Vim\%((\a\+)\)\=:E117/ 11 | let s:has_vimproc = 0 12 | endtry 13 | endif 14 | 15 | if !s:has_vimproc 16 | echohl ErrorMsg 17 | echomsg 'ghcmod: vimproc.vim is not installed!' 18 | echohl None 19 | finish 20 | endif 21 | 22 | if !exists('s:has_ghc_mod') 23 | let s:has_ghc_mod = 0 24 | 25 | if !executable('ghc-mod') 26 | call ghcmod#util#print_error('ghcmod: ghc-mod is not executable!') 27 | finish 28 | endif 29 | 30 | let s:required_version = [5, 0, 0] 31 | if !ghcmod#util#check_version(s:required_version) 32 | call ghcmod#util#print_error(printf('ghcmod: requires ghc-mod %s or higher', join(s:required_version, '.'))) 33 | finish 34 | endif 35 | 36 | let s:has_ghc_mod = 1 37 | endif 38 | 39 | if !s:has_ghc_mod 40 | finish 41 | endif 42 | 43 | if exists('b:undo_ftplugin') 44 | let b:undo_ftplugin .= ' | ' 45 | else 46 | let b:undo_ftplugin = '' 47 | endif 48 | 49 | command! -buffer -nargs=0 -bang GhcModType call ghcmod#command#type(0) 50 | command! -buffer -nargs=0 -bang GhcModTypeInsert call ghcmod#command#type_insert(0) 51 | command! -buffer -nargs=0 -bang GhcModSplitFunCase call ghcmod#command#split_function_case(0) 52 | command! -buffer -nargs=0 -bang GhcModSigCodegen call ghcmod#command#initial_code_from_signature(0) 53 | command! -buffer -nargs=? -bang GhcModInfo call ghcmod#command#info(, 0) 54 | command! -buffer -nargs=0 GhcModTypeClear call ghcmod#command#type_clear() 55 | command! -buffer -nargs=? -bang GhcModInfoPreview call ghcmod#command#info_preview(, 0) 56 | command! -buffer -nargs=0 -bang GhcModCheck call ghcmod#command#make('check', 0) 57 | command! -buffer -nargs=0 -bang GhcModLint call ghcmod#command#make('lint', 0) 58 | command! -buffer -nargs=0 -bang GhcModCheckAsync call ghcmod#command#async_make('check', 0) 59 | command! -buffer -nargs=0 -bang GhcModLintAsync call ghcmod#command#async_make('lint', 0) 60 | command! -buffer -nargs=0 -bang GhcModCheckAndLintAsync call ghcmod#command#check_and_lint_async(0) 61 | command! -buffer -nargs=0 -bang GhcModExpand call ghcmod#command#expand(0) 62 | let b:undo_ftplugin .= join(map([ 63 | \ 'GhcModType', 64 | \ 'GhcModTypeInsert', 65 | \ 'GhcModSplitFunCase', 66 | \ 'GhcModSigCodegen', 67 | \ 'GhcModInfo', 68 | \ 'GhcModInfoPreview', 69 | \ 'GhcModTypeClear', 70 | \ 'GhcModCheck', 71 | \ 'GhcModLint', 72 | \ 'GhcModCheckAsync', 73 | \ 'GhcModLintAsync', 74 | \ 'GhcModCheckAndLintAsync', 75 | \ 'GhcModExpand' 76 | \ ], '"delcommand " . v:val'), ' | ') 77 | let b:undo_ftplugin .= ' | unlet b:did_ftplugin_ghcmod' 78 | 79 | " Ensure syntax highlighting for ghcmod#detect_module() 80 | syntax sync fromstart 81 | 82 | " vim: set ts=2 sw=2 et fdm=marker: 83 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | ## 1.3.1 (2015-07-26) 3 | - Fix out of scope variable in ghcmod#util#tocol (#74, @jimenezrick) 4 | - Fix base directory calculation in deep subdirectories (#75) 5 | - Fix ghc-mod feature detection with ghc-mod's master build (#66) 6 | - Fix locations in quickfix list for `ghc-mod expand` 7 | 8 | ## 1.3.0 (2015-03-17) 9 | - Add `:GhcModDiagnostics` command 10 | - Allow whitespaces in filename (#43) 11 | - Add newlines to ghc-mod's info command (#51, @cdepillabout) 12 | - Fix detection of version of ghc-mod (#57, @ts468) 13 | - Require ghc-mod >= 4.1.0 14 | - Fix type insertion for top level types (#70, @eddking) 15 | - Fix `:GhcModType` and `:GhcModTypeInsert` for hard tabs (#71, @itchyny) 16 | 17 | ## 1.2.0 (2014-02-02) 18 | - Support Cabal sandbox (@andy-morris, #33) 19 | - Support lhaskell (@carlohamalainen, #36) 20 | - Add another way to open the quickfix (@shiena, #37) 21 | - Workaround `ghcmod#util#is_abspath()` on Windows with Vim < 7.4.001 (@Grivr, #41) 22 | 23 | ## 1.1.0 (2013-10-16) 24 | - Specify line separator by "-b" option (@preygel, #32) 25 | - This requires ghc-mod >= 2.1.2 26 | 27 | ## 1.0.0 (2013-04-25) 28 | - Refactor many autoload functions 29 | - ghcmod.vim is an interface to ghc-mod 30 | - ghcmod/command.vim contains definitions of commands 31 | - ghcmod/type.vim is an implementation of `:GhcModType` 32 | - Now `w:ghcmod_type_matchid` is not needed and deleted 33 | - ghcmod/util.vim is a collection of utilities 34 | - Add test cases 35 | - Speed up boot time by moving autoload functions used at loading ftplugins 36 | - Add commands with `!` that is executed even if the current buffer is modified 37 | - Show splicing end locations in `:GhcModExpand` 38 | - Fix escaping in `:GhcModInfoPreview` 39 | - Fix ignorance of `g:ghcmod_ghc_options` 40 | 41 | ## 0.4.0 (2013-03-13) 42 | - Suppress empty line when `ghcmod#type()` fails 43 | - Disable `:GhcModType` and `:GhcModTypeInsert` if the current buffer is modified 44 | - Add new variant `:GhcModType!` and `:GhcModTypeInsert!` which is executed even if the current buffer is modified. 45 | - Change `ghcmod#type()` and `ghcmod#type_insert()` to take an argument determining the behavior when the buffer is modified. 46 | - Fix `ghcmod#detect_module()` to detect the module name more correctly 47 | - Change the default directory to execute ghc-mod from (@drchaos, #25) 48 | 49 | ## 0.3.0 (2013-03-06) 50 | - Add `:GhcModTypeInsert` and `ghcmod#type_insert()` that inserts a type signature under the cursor (@johntyree, #15) 51 | - Add `:GhcModInfoPreview` that shows information in preview window (@johntyree, #15) 52 | - Make the parsing of check command more robust (@marcmo, #21) 53 | - Add buffer-local version of `g:ghcmod_ghc_options` 54 | 55 | ## 0.2.0 (2012-09-12) 56 | - Fix the wrong comparison of versions in `ghcmod#check_version` (@yuttie, #11) 57 | - Fix `ghcmod#type()` on a program with compilation errors 58 | - Add `g:ghcmod_use_basedir` option (@adimit, #12) 59 | - Add `:GhcModInfo [{identifier}]` command (@ajnsit, #14) 60 | 61 | ## 0.1.2 (2012-06-04) 62 | - Move ftplugin/haskell into after/ in order to co-exist with other ftplugins 63 | - Fix `s:join_path()` for Windows 64 | 65 | ## 0.1.1 (2012-04-02) 66 | - Fix the location of vimproc.vim 67 | 68 | ## 0.1.0 (2012-04-01) 69 | - Initial release 70 | -------------------------------------------------------------------------------- /test/test_check.vim: -------------------------------------------------------------------------------- 1 | function! s:normalize(qflist) 2 | for l:qf in a:qflist 3 | if has_key(l:qf, 'filename') 4 | let l:qf.filename = fnamemodify(l:qf.filename, ':.') 5 | endif 6 | endfor 7 | return a:qflist 8 | endfunction 9 | 10 | function! s:async(callback) 11 | let l:callback = { 'base': a:callback } 12 | function! l:callback.on_finish(qflist) 13 | let self.active = 0 14 | call self.base.on_finish(a:qflist) 15 | endfunction 16 | 17 | let l:callback.active = 1 18 | call ghcmod#async_make('check', expand('%:p'), l:callback) 19 | while l:callback.active 20 | sleep 100m 21 | " XXX: 22 | doautocmd CursorHold 23 | endwhile 24 | endfunction 25 | 26 | function! s:make_qf_pred(qf) 27 | let l:pred = deepcopy(a:qf) 28 | function! l:pred.call(qf) 29 | for l:key in ['lnum', 'col', 'type', 'filename'] 30 | if self[l:key] != a:qf[l:key] 31 | return 0 32 | endif 33 | endfor 34 | return 1 35 | endfunction 36 | return l:pred 37 | endfunction 38 | 39 | let s:unit = tinytest#new() 40 | 41 | function! s:unit.teardown() 42 | bdelete 43 | endfunction 44 | 45 | function! s:unit.test_check() 46 | new test/data/with-cabal/src/Foo.hs 47 | let l:qflist = ghcmod#make('check', expand('%:p')) 48 | call s:normalize(l:qflist) 49 | call self.assert.any(s:make_qf_pred({ 50 | \ 'lnum': 3, 'col': 1, 'type': 'W', 51 | \ 'filename': 'test/data/with-cabal/src/Foo/Bar.hs', 52 | \ }), l:qflist) 53 | call self.assert.any(s:make_qf_pred({ 54 | \ 'lnum': 4, 'col': 1, 'type': 'W', 55 | \ 'filename': 'test/data/with-cabal/src/Foo.hs', 56 | \ }), l:qflist) 57 | endfunction 58 | 59 | function! s:unit.test_check_whitespace() 60 | new test/data/with\ whitespace/src/Foo.hs 61 | let l:qflist = ghcmod#make('check', expand('%:p')) 62 | call s:normalize(l:qflist) 63 | call self.assert.any(s:make_qf_pred({ 64 | \ 'lnum': 3, 'col': 1, 'type': 'W', 65 | \ 'filename': 'test/data/with whitespace/src/Foo/Bar.hs', 66 | \ }), l:qflist) 67 | call self.assert.any(s:make_qf_pred({ 68 | \ 'lnum': 4, 'col': 1, 'type': 'W', 69 | \ 'filename': 'test/data/with whitespace/src/Foo.hs', 70 | \ }), l:qflist) 71 | endfunction 72 | 73 | function! s:unit.test_check_compilation_error() 74 | new test/data/failure/Main.hs 75 | let l:qflist = ghcmod#make('check', expand('%:p')) 76 | call s:normalize(l:qflist) 77 | call self.assert.any(s:make_qf_pred({ 78 | \ 'lnum': 3, 'col': 22, 'type': 'E', 79 | \ 'filename': 'test/data/failure/Main.hs', 80 | \ }), l:qflist) 81 | endfunction 82 | 83 | function! s:unit.test_check_async() 84 | new test/data/with-cabal/src/Foo.hs 85 | let l:callback = { 'assert': self.assert } 86 | function! l:callback.on_finish(qflist) 87 | call s:normalize(a:qflist) 88 | call self.assert.any(s:make_qf_pred({ 89 | \ 'lnum': 3, 'col': 1, 'type': 'W', 90 | \ 'filename': 'test/data/with-cabal/src/Foo/Bar.hs', 91 | \ }), a:qflist) 92 | call self.assert.any(s:make_qf_pred({ 93 | \ 'lnum': 4, 'col': 1, 'type': 'W', 94 | \ 'filename': 'test/data/with-cabal/src/Foo.hs', 95 | \ }), a:qflist) 96 | endfunction 97 | call s:async(l:callback) 98 | endfunction 99 | 100 | function! s:unit.test_check_async_compilation_error() 101 | new test/data/failure/Main.hs 102 | let l:callback = { 'assert': self.assert } 103 | function! l:callback.on_finish(qflist) 104 | call s:normalize(a:qflist) 105 | call self.assert.any(s:make_qf_pred({ 106 | \ 'lnum': 3, 'col': 22, 'type': 'E', 107 | \ 'filename': 'test/data/failure/Main.hs', 108 | \ }), a:qflist) 109 | endfunction 110 | call s:async(l:callback) 111 | endfunction 112 | 113 | call s:unit.run() 114 | -------------------------------------------------------------------------------- /autoload/ghcmod/util.vim: -------------------------------------------------------------------------------- 1 | function! ghcmod#util#print_warning(msg) "{{{ 2 | echohl WarningMsg 3 | echomsg a:msg 4 | echohl None 5 | endfunction "}}} 6 | 7 | function! ghcmod#util#print_error(msg) "{{{ 8 | echohl ErrorMsg 9 | echomsg a:msg 10 | echohl None 11 | endfunction "}}} 12 | 13 | if vimproc#util#is_windows() " s:is_abspath {{{ 14 | if v:version > 704 || (v:version == 704 && has('patch001')) 15 | function! ghcmod#util#is_abspath(path) 16 | return a:path =~? '^[a-z]:[\/]' 17 | endfunction 18 | else 19 | " NFA regexp engine had a bug and fixed in 7.4.001. 20 | " http://code.google.com/p/vim/source/detail?r=3e9107b86b68d83bfa94e43afffbf17623afe55e 21 | function! ghcmod#util#is_abspath(path) 22 | return a:path =~# '^[A-Za-z]:[\/]' 23 | endfunction 24 | endif 25 | else 26 | function! ghcmod#util#is_abspath(path) 27 | return a:path[0] ==# '/' 28 | endfunction 29 | endif "}}} 30 | 31 | if v:version > 703 || (v:version == 703 && has('patch465')) "{{{ 32 | function! ghcmod#util#globlist(pat) 33 | return glob(a:pat, 0, 1) 34 | endfunction 35 | else 36 | function! ghcmod#util#globlist(pat) 37 | return split(glob(a:pat, 0), '\n') 38 | endfunction 39 | endif "}}} 40 | 41 | function! ghcmod#util#join_path(dir, path) "{{{ 42 | if ghcmod#util#is_abspath(a:path) 43 | return a:path 44 | else 45 | return a:dir . '/' . a:path 46 | endif 47 | endfunction "}}} 48 | 49 | function! ghcmod#util#getcol() "{{{ 50 | let l:line = line('.') 51 | let l:col = col('.') 52 | let l:str = getline(l:line)[:(l:col - 1)] 53 | let l:tabcnt = len(substitute(l:str, '[^\t]', '', 'g')) 54 | return l:col + 7 * l:tabcnt 55 | endfunction "}}} 56 | 57 | function! ghcmod#util#tocol(line, col) "{{{ 58 | let l:str = getline(a:line) 59 | let l:len = len(l:str) 60 | let l:col = 0 61 | for l:i in range(1, l:len) 62 | let l:col += (l:str[l:i - 1] ==# "\t" ? 8 : 1) 63 | if l:col >= a:col 64 | return l:i 65 | endif 66 | endfor 67 | return l:len + 1 68 | endfunction "}}} 69 | 70 | function! ghcmod#util#wait(proc) "{{{ 71 | if has_key(a:proc, 'checkpid') 72 | return a:proc.checkpid() 73 | else 74 | " old vimproc 75 | if !exists('s:libcall') 76 | redir => l:output 77 | silent! scriptnames 78 | redir END 79 | for l:line in split(l:output, '\n') 80 | if l:line =~# 'autoload/vimproc\.vim$' 81 | let s:libcall = function('' . matchstr(l:line, '^\s*\zs\d\+') . '_libcall') 82 | break 83 | endif 84 | endfor 85 | endif 86 | return s:libcall('vp_waitpid', [a:proc.pid]) 87 | endif 88 | endfunction "}}} 89 | 90 | function! ghcmod#util#check_version(version) "{{{ 91 | let l:ghc_mod_version = ghcmod#util#ghc_mod_version() 92 | if l:ghc_mod_version == [0, 0, 0] 93 | " 'version 0' should support all features. 94 | return 1 95 | end 96 | 97 | for l:i in range(0, 2) 98 | if a:version[l:i] > l:ghc_mod_version[l:i] 99 | return 0 100 | elseif a:version[l:i] < l:ghc_mod_version[l:i] 101 | return 1 102 | endif 103 | endfor 104 | return 1 105 | endfunction "}}} 106 | 107 | function! ghcmod#util#ghc_mod_version() "{{{ 108 | if !exists('s:ghc_mod_version') 109 | let l:ghcmod = vimproc#system(['ghc-mod','version']) 110 | let l:m = matchlist(l:ghcmod, 'version \(\d\+\)\.\(\d\+\)\.\(\d\+\)') 111 | if empty(l:m) 112 | if match(l:ghcmod, 'version 0 ') == -1 113 | call ghcmod#util#print_error(printf('ghcmod-vim: Cannot detect ghc-mod version from %s', l:ghcmod)) 114 | else 115 | " 'version 0' means master 116 | " https://github.com/eagletmt/ghcmod-vim/issues/66 117 | let s:ghc_mod_version = [0, 0, 0] 118 | endif 119 | else 120 | let s:ghc_mod_version = l:m[1 : 3] 121 | call map(s:ghc_mod_version, 'str2nr(v:val)') 122 | endif 123 | endif 124 | return s:ghc_mod_version 125 | endfunction "}}} 126 | 127 | " vim: set ts=2 sw=2 et fdm=marker: 128 | -------------------------------------------------------------------------------- /test/test_lint.vim: -------------------------------------------------------------------------------- 1 | function! s:normalize(qflist) 2 | for l:qf in a:qflist 3 | if has_key(l:qf, 'filename') 4 | let l:qf.filename = fnamemodify(l:qf.filename, ':.') 5 | endif 6 | endfor 7 | return a:qflist 8 | endfunction 9 | 10 | function! s:async(callback) 11 | let l:callback = { 'base': a:callback } 12 | function! l:callback.on_finish(qflist) 13 | let self.active = 0 14 | call self.base.on_finish(a:qflist) 15 | endfunction 16 | 17 | let l:callback.active = 1 18 | call ghcmod#async_make('lint', expand('%:p'), l:callback) 19 | while l:callback.active 20 | sleep 100m 21 | " XXX: 22 | doautocmd CursorHold 23 | endwhile 24 | endfunction 25 | 26 | let s:unit = tinytest#new() 27 | 28 | function! s:unit.teardown() 29 | bdelete 30 | endfunction 31 | 32 | function! s:make_qf_pred(qf) 33 | let l:pred = deepcopy(a:qf) 34 | let l:pred.__qf = a:qf 35 | function! l:pred.call(qf) 36 | if !has_key(a:qf, 'type') 37 | return 0 38 | endif 39 | for l:key in keys(self.__qf) 40 | if self[l:key] != a:qf[l:key] 41 | return 0 42 | endif 43 | endfor 44 | return 1 45 | endfunction 46 | return l:pred 47 | endfunction 48 | 49 | function! s:unit.test_lint() 50 | edit test/data/with-cabal/src/Foo/Bar.hs 51 | let l:qflist = s:normalize(ghcmod#make('lint', expand('%:p'))) 52 | call self.assert.any(s:make_qf_pred({ 53 | \ 'lnum': 5, 54 | \ 'col': 9, 55 | \ 'filename': 'test/data/with-cabal/src/Foo/Bar.hs', 56 | \ 'text': 'Evaluate', 57 | \ }), l:qflist) 58 | call self.assert.any(s:make_qf_pred({ 59 | \ 'lnum': 5, 60 | \ 'col': 9, 61 | \ 'filename': 'test/data/with-cabal/src/Foo/Bar.hs', 62 | \ 'text': 'Redundant $', 63 | \ }), l:qflist) 64 | endfunction 65 | 66 | function! s:unit.test_lint_whitespace() 67 | edit test/data/with\ whitespace/src/Foo/Bar.hs 68 | let l:qflist = s:normalize(ghcmod#make('lint', expand('%:p'))) 69 | call self.assert.any(s:make_qf_pred({ 70 | \ 'lnum': 5, 71 | \ 'col': 9, 72 | \ 'filename': 'test/data/with whitespace/src/Foo/Bar.hs', 73 | \ 'text': 'Evaluate', 74 | \ }), l:qflist) 75 | call self.assert.any(s:make_qf_pred({ 76 | \ 'lnum': 5, 77 | \ 'col': 9, 78 | \ 'filename': 'test/data/with whitespace/src/Foo/Bar.hs', 79 | \ 'text': 'Redundant $', 80 | \ }), l:qflist) 81 | endfunction 82 | 83 | function! s:unit.test_lint_async() 84 | edit test/data/with-cabal/src/Foo/Bar.hs 85 | let l:callback = { 'assert': self.assert } 86 | function! l:callback.on_finish(qflist) 87 | call s:normalize(a:qflist) 88 | call self.assert.any(s:make_qf_pred({ 89 | \ 'lnum': 5, 90 | \ 'col': 9, 91 | \ 'filename': 'test/data/with-cabal/src/Foo/Bar.hs', 92 | \ 'text': 'Evaluate', 93 | \ }), a:qflist) 94 | call self.assert.any(s:make_qf_pred({ 95 | \ 'lnum': 5, 96 | \ 'col': 9, 97 | \ 'filename': 'test/data/with-cabal/src/Foo/Bar.hs', 98 | \ 'text': 'Redundant $', 99 | \ }), a:qflist) 100 | endfunction 101 | call s:async(l:callback) 102 | endfunction 103 | 104 | function! s:unit.test_lint_opt() 105 | let g:ghcmod_hlint_options = ['-i', 'Evaluate'] 106 | try 107 | edit test/data/with-cabal/src/Foo/Bar.hs 108 | let l:qflist = s:normalize(ghcmod#make('lint', expand('%:p'))) 109 | call self.assert.none(s:make_qf_pred({ 'text': 'Evaluate' }), l:qflist) 110 | call self.assert.any(s:make_qf_pred({ 111 | \ 'lnum': 5, 112 | \ 'col': 9, 113 | \ 'filename': 'test/data/with-cabal/src/Foo/Bar.hs', 114 | \ 'text': 'Redundant $', 115 | \ }), l:qflist) 116 | finally 117 | unlet g:ghcmod_hlint_options 118 | endtry 119 | endfunction 120 | 121 | function! s:unit.test_lint_async_opt() 122 | let g:ghcmod_hlint_options = ['-i', 'Evaluate'] 123 | edit test/data/with-cabal/src/Foo/Bar.hs 124 | let l:callback = { 'assert': self.assert } 125 | function! l:callback.on_finish(qflist) 126 | unlet g:ghcmod_hlint_options 127 | call s:normalize(a:qflist) 128 | call self.assert.none(s:make_qf_pred({ 'text': 'Evaluate' }), a:qflist) 129 | call self.assert.any(s:make_qf_pred({ 130 | \ 'lnum': 5, 131 | \ 'col': 9, 132 | \ 'filename': 'test/data/with-cabal/src/Foo/Bar.hs', 133 | \ 'text': 'Redundant $', 134 | \ }), a:qflist) 135 | endfunction 136 | call s:async(l:callback) 137 | endfunction 138 | 139 | call s:unit.run() 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ghcmod.vim 2 | [![Build Status](https://api.travis-ci.org/eagletmt/ghcmod-vim.svg)](https://travis-ci.org/eagletmt/ghcmod-vim) 3 | [![Gitter chat](https://badges.gitter.im/eagletmt/ghcmod-vim.png)](https://gitter.im/eagletmt/ghcmod-vim) 4 | 5 | Happy Haskell programming on Vim, powered by [ghc-mod](https://github.com/kazu-yamamoto/ghc-mod) 6 | 7 | - [http://www.vim.org/scripts/script.php?script\_id=4473](http://www.vim.org/scripts/script.php?script_id=4473) 8 | - https://github.com/eagletmt/ghcmod-vim/releases 9 | 10 | ## Features 11 | 12 | - Displaying the type of sub-expressions (`ghc-mod type`) 13 | - Displaying error/warning messages and their locations (`ghc-mod check` and `ghc-mod lint`) 14 | - Displaying the expansion of splices (`ghc-mod expand`) 15 | - Insert split function cases (`ghc-mod split`) 16 | 17 | Completions are supported by another plugin. 18 | See [neco-ghc](https://github.com/eagletmt/neco-ghc) . 19 | 20 | ## Requirements 21 | 22 | ### Vim 23 | ghcmod.vim contains ftplugin. 24 | Please make sure that filetype plugin is enabled. 25 | To check it, type `:filetype` and you would see something like this: `filetype detection:ON plugin:ON indent:ON`. 26 | You can enable it by `:filetype plugin on`. 27 | I highly recommend adding `filetype plugin indent on` to your vimrc. 28 | See `:help :filetype-overview` for more details. 29 | 30 | ### vimproc 31 | https://github.com/Shougo/vimproc 32 | 33 | ### ghc-mod >= 5.0.0 34 | ```sh 35 | cabal install ghc-mod 36 | ``` 37 | 38 | ## Details 39 | If you'd like to give GHC options, set `g:ghcmod_ghc_options`. 40 | 41 | ```vim 42 | let g:ghcmod_ghc_options = ['-idir1', '-idir2'] 43 | ``` 44 | 45 | Also, there's buffer-local version `b:ghcmod_ghc_options`. 46 | 47 | ```vim 48 | autocmd BufRead,BufNewFile ~/.xmonad/* call s:add_xmonad_path() 49 | function! s:add_xmonad_path() 50 | if !exists('b:ghcmod_ghc_options') 51 | let b:ghcmod_ghc_options = [] 52 | endif 53 | call add(b:ghcmod_ghc_options, '-i' . expand('~/.xmonad/lib')) 54 | endfunction 55 | ``` 56 | 57 | ### :GhcModType, :GhcModTypeClear 58 | Type `:GhcModType` on a expression, then the sub-expression is highlighted and its type is echoed. 59 | If you type `:GhcModType` multiple times, the sub-expression changes. 60 | 61 | 1. ![type1](http://cache.gyazo.com/361ad3652a412f780106ab07ad11f206.png) 62 | 2. ![type2](http://cache.gyazo.com/0c884849a971e367c75a6ba68bed0157.png) 63 | 3. ![type3](http://cache.gyazo.com/3644d66a3c5fbc51c01b5bb2053864cd.png) 64 | 4. ![type4](http://cache.gyazo.com/ece85e8a1250bebfd13208a63679a3db.png) 65 | 5. ![type5](http://cache.gyazo.com/71e4c79f9b42faaaf81b4e3695fb4d7f.png) 66 | 67 | Since ghc-mod 1.10.8, not only sub-expressions but name bindings and sub-patterns are supported. 68 | 69 | - ![type-bind](http://cache.gyazo.com/cee203adbf715f00d2dbd82c5cff3eaa.png) 70 | - ![type-pat](http://cache.gyazo.com/7a22068b73442e8447a4081d5ddffd31.png) 71 | 72 | Type `:GhcModTypeClear` to clear sub-expression's highlight. 73 | 74 | Sub-expressions are highlighted as `Search` by default. 75 | You can customize it by setting `g:ghcmod_type_highlight` . 76 | 77 | ```vim 78 | hi ghcmodType ctermbg=yellow 79 | let g:ghcmod_type_highlight = 'ghcmodType' 80 | ``` 81 | 82 | ### :GhcModCheck, :GhcModLint 83 | You can get compiler errors/warnings by `:GhcModCheck` and they are available in quickfix window. 84 | 85 | ![check](http://cache.gyazo.com/c09399b2fe370ce9d328b8ed12118de8.png) 86 | 87 | Similarly, type `:GhcModLint` to get suggestions by `ghc-mod lint`. 88 | 89 | If you'd like to pass options to hlint, set `g:ghcmod_hlint_options`. 90 | 91 | ```vim 92 | let g:ghcmod_hlint_options = ['--ignore=Redundant $'] 93 | ``` 94 | 95 | ![lint](http://cache.gyazo.com/3b64724ce2587e03761fe618457f1c2e.png) 96 | 97 | If you'd like to open in another way the quickfix, set `g:ghcmod_open_quickfix_function`. 98 | 99 | ```vim 100 | let g:ghcmod_open_quickfix_function = 'GhcModQuickFix' 101 | function! GhcModQuickFix() 102 | " for unite.vim and unite-quickfix 103 | :Unite -no-empty quickfix 104 | 105 | " for ctrlp 106 | ":CtrlPQuickfix 107 | 108 | " for FuzzyFinder 109 | ":FufQuickfix 110 | endfunction 111 | ``` 112 | 113 | ### :GhcModCheckAsync, :GhcModLintAsync, :GhcModCheckAndLintAsync 114 | You can run check and/or lint asynchronously. 115 | 116 | This would be useful when you'd like to run check and/or lint automatically (especially on `BufWritePost`). 117 | See Customize wiki page for more detail. 118 | 119 | ### :GhcModExpand 120 | You can see the expansion of splices by `:GhcModExpand` and they are available in quickfix window. 121 | 122 | ![expand](http://cache.gyazo.com/bcbee2b84f956a87b636a67b5d5af488.png) 123 | 124 | This feature was introduced since ghc-mod 1.10.10. 125 | 126 | ### GhcModSplitFunCase 127 | Split the function case by examining a type's constructors. 128 | 129 | ```haskell 130 | f :: [a] -> a 131 | f x = _body 132 | ``` 133 | 134 | When you type `:GhcModSplitFunCase` at the `x` position, ghcmod-vim will replace it with: 135 | 136 | ```haskell 137 | f :: [a] -> a 138 | f [] = _body 139 | f (x:xs) = _body 140 | ``` 141 | 142 | ### GhcModSigCodegen 143 | Insert initial code from the given signature. 144 | 145 | ```haskell 146 | func :: [a] -> Maybe b -> (a -> b) -> (a,b) 147 | ``` 148 | 149 | ghcmod-vim will insert initial code using typed holes. 150 | 151 | ```haskell 152 | func x y z f = _func_body 153 | ``` 154 | 155 | Instance declarations are also supported. 156 | 157 | ```haskell 158 | newtype D = D (Int,String) 159 | 160 | class C a where 161 | cInt :: a -> Int 162 | cString :: a -> String 163 | 164 | instance C D where 165 | ``` 166 | 167 | ghcmod-vim will insert: 168 | 169 | ```haskell 170 | cInt x = _cInt_body 171 | cString x = _cString_body 172 | ``` 173 | 174 | ## Customize 175 | See wiki page [Customize](https://github.com/eagletmt/ghcmod-vim/wiki/Customize). 176 | 177 | ## License 178 | [BSD3 License](http://www.opensource.org/licenses/BSD-3-Clause), the same license as ghc-mod. 179 | 180 | Copyright (c) 2012-2013, eagletmt 181 | -------------------------------------------------------------------------------- /autoload/ghcmod/command.vim: -------------------------------------------------------------------------------- 1 | function! s:buffer_path(force) "{{{ 2 | let l:path = expand('%:p') 3 | if empty(l:path) 4 | call ghcmod#util#print_warning("current version of ghcmod.vim doesn't support running on an unnamed buffer.") 5 | return '' 6 | endif 7 | 8 | if &l:modified 9 | let l:msg = 'ghcmod.vim: the current buffer has been modified but not written' 10 | if a:force 11 | call ghcmod#util#print_warning(l:msg) 12 | else 13 | call ghcmod#util#print_error(l:msg) 14 | return '' 15 | endif 16 | endif 17 | 18 | return l:path 19 | endfunction "}}} 20 | 21 | function! ghcmod#command#type(force) "{{{ 22 | let l:line = line('.') 23 | let l:col = ghcmod#util#getcol() 24 | 25 | if exists('b:ghcmod_type') 26 | if b:ghcmod_type.spans(l:line, l:col) 27 | call b:ghcmod_type.incr_ix() 28 | call b:ghcmod_type.highlight() 29 | echo b:ghcmod_type.type() 30 | return 31 | endif 32 | call b:ghcmod_type.clear_highlight() 33 | endif 34 | 35 | let l:path = s:buffer_path(a:force) 36 | if empty(l:path) 37 | return 38 | endif 39 | 40 | let l:types = ghcmod#type(l:line, l:col, l:path) 41 | if empty(l:types) 42 | call ghcmod#util#print_error('ghcmod#command#type: Cannot guess type') 43 | return 44 | endif 45 | 46 | let b:ghcmod_type = ghcmod#type#new(l:types, ghcmod#highlight_group()) 47 | call b:ghcmod_type.highlight() 48 | 49 | echo b:ghcmod_type.type() 50 | endfunction "}}} 51 | 52 | function! ghcmod#command#type_clear() "{{{ 53 | if exists('b:ghcmod_type') 54 | call b:ghcmod_type.clear_highlight() 55 | unlet b:ghcmod_type 56 | endif 57 | endfunction "}}} 58 | 59 | function! ghcmod#command#split_function_case(force) "{{{ 60 | let l:path = s:buffer_path(a:force) 61 | if empty(l:path) 62 | return 63 | endif 64 | 65 | let l:decls = ghcmod#split(line('.'), col('.'), l:path) 66 | if empty(l:decls) 67 | call ghcmod#util#print_warning('No splittable constructor') 68 | return 69 | endif 70 | 71 | call append(line('.'), l:decls) 72 | delete _ 73 | endfunction "}}} 74 | 75 | function! ghcmod#command#initial_code_from_signature(force) "{{{ 76 | let l:path = s:buffer_path(a:force) 77 | if empty(l:path) 78 | return 79 | endif 80 | 81 | let l:initial_code = ghcmod#sig(line('.'), col('.'), l:path) 82 | if empty(l:initial_code) 83 | call ghcmod#util#print_warning('Cannot generate initial code') 84 | return 85 | endif 86 | 87 | let [l:sort, l:codes] = l:initial_code 88 | if l:sort == 'instance' 89 | let l:sw = exists('*shifwidth') ? shiftwidth() : &shiftwidth 90 | let l:indent = repeat(' ', l:sw) 91 | call map(l:codes, 'l:indent . v:val') 92 | endif 93 | call append('.', l:codes) 94 | endfunction "}}} 95 | 96 | function! ghcmod#command#type_insert(force) "{{{ 97 | let l:path = s:buffer_path(a:force) 98 | if empty(l:path) 99 | return 100 | endif 101 | 102 | let l:fexp = ghcmod#getHaskellIdentifier() 103 | if empty(l:fexp) 104 | call ghcmod#util#print_error('Failed to determine identifier under cursor.') 105 | return 106 | endif 107 | 108 | let l:types = ghcmod#type(line('.'), ghcmod#util#getcol(), l:path) 109 | if empty(l:types) " Everything failed so let's just abort 110 | call ghcmod#util#print_error('ghcmod#command#type_insert: Cannot guess type') 111 | return 112 | endif 113 | let [l:locsym, l:type] = l:types[0] 114 | let l:signature = printf('%s :: %s', l:fexp, l:type) 115 | let [_, l:offset, _, _] = l:locsym 116 | 117 | if l:offset == 1 " We're doing top-level, let's try to use :info instead 118 | let l:info = ghcmod#info(l:fexp, l:path) 119 | if !empty(l:info) " Continue only if we don't find errors 120 | let l:info = substitute(l:info, '\n\|\t.*', "", "g") " Remove extra lines 121 | let l:info = substitute(l:info, '\s\+', " ", "g") " Compress whitespace 122 | let l:info = substitute(l:info, '\s\+$', "", "g") " Remove trailing whitespace 123 | let l:signature = l:info 124 | endif 125 | endif 126 | call append(line(".")-1, repeat(' ', l:offset-1) . l:signature) 127 | endfunction "}}} 128 | 129 | function! s:info(fexp, force) "{{{ 130 | let l:path = s:buffer_path(a:force) 131 | if empty(l:path) 132 | return 133 | endif 134 | let l:fexp = a:fexp 135 | if empty(l:fexp) 136 | let l:fexp = ghcmod#getHaskellIdentifier() 137 | end 138 | return ghcmod#info(l:fexp, l:path) 139 | endfunction "}}} 140 | 141 | function! ghcmod#command#info(fexp, force) "{{{ 142 | let l:info = s:info(a:fexp, a:force) 143 | if !empty(l:info) 144 | echo l:info 145 | endif 146 | endfunction "}}} 147 | 148 | function! ghcmod#command#info_preview(fexp, force, ...) "{{{ 149 | let l:info = s:info(a:fexp, a:force) 150 | if empty(l:info) 151 | return 152 | endif 153 | 154 | if a:0 == 0 155 | let l:size = get(g:, 'ghcmod_max_preview_size', 10) 156 | else 157 | let l:size = a:000[0] 158 | endif 159 | 160 | silent! wincmd P 161 | if !(&previewwindow && expand("%:t") == "GHC-mod") 162 | pclose 163 | pedit GHC-mod 164 | silent! wincmd P 165 | endif 166 | setlocal modifiable 167 | setlocal buftype=nofile 168 | " make sure buffer is deleted when view is closed 169 | setlocal bufhidden=wipe 170 | setlocal noswapfile 171 | setlocal nobuflisted 172 | setlocal nonumber 173 | setlocal statusline=%F 174 | setlocal nofoldenable 175 | setlocal filetype=haskell 176 | setlocal nolist 177 | silent 0put =l:info 178 | setlocal nomodifiable 179 | exec 'resize ' . min([line('$')+1, l:size]) 180 | normal! gg 181 | wincmd p 182 | endfunction "}}} 183 | 184 | function! ghcmod#command#make(type, force) "{{{ 185 | let l:path = s:buffer_path(a:force) 186 | if empty(l:path) 187 | return 188 | endif 189 | 190 | let l:qflist = ghcmod#make(a:type, l:path) 191 | call setqflist(l:qflist) 192 | call s:open_quickfix() 193 | if empty(l:qflist) 194 | echo printf('ghc-mod %s: No errors found', a:type) 195 | endif 196 | endfunction "}}} 197 | 198 | function! ghcmod#command#async_make(type, force) "{{{ 199 | let l:path = s:buffer_path(a:force) 200 | if empty(l:path) 201 | return 202 | endif 203 | 204 | let l:callback = { 'type': a:type } 205 | function! l:callback.on_finish(qflist) 206 | call setqflist(a:qflist) 207 | call s:open_quickfix() 208 | if &l:buftype ==# 'quickfix' 209 | " go back to original window 210 | wincmd p 211 | endif 212 | if empty(a:qflist) 213 | echomsg printf('ghc-mod %s(async): No errors found', self.type) 214 | endif 215 | endfunction 216 | 217 | call ghcmod#async_make(a:type, l:path, l:callback) 218 | endfunction "}}} 219 | 220 | function! ghcmod#command#check_and_lint_async(force) "{{{ 221 | let l:path = s:buffer_path(a:force) 222 | if empty(l:path) 223 | return 224 | endif 225 | 226 | let l:callback = { 'first': 1 } 227 | function! l:callback.on_finish(qflist) 228 | if self.first 229 | call setqflist(a:qflist) 230 | let self.first = 0 231 | else 232 | call setqflist(a:qflist, 'a') 233 | call s:open_quickfix() 234 | if &l:buftype ==# 'quickfix' 235 | " go back to original window 236 | wincmd p 237 | endif 238 | if empty(getqflist()) 239 | echomsg 'ghc-mod check and lint(async): No errors found' 240 | endif 241 | endif 242 | endfunction 243 | 244 | if !ghcmod#async#exist_session() 245 | call ghcmod#async_make('check', l:path, l:callback) 246 | call ghcmod#async_make('lint', l:path, l:callback) 247 | endif 248 | endfunction "}}} 249 | 250 | function! ghcmod#command#expand(force) "{{{ 251 | let l:path = s:buffer_path(a:force) 252 | if empty(l:path) 253 | return 254 | endif 255 | 256 | call setqflist(ghcmod#expand(l:path)) 257 | call s:open_quickfix() 258 | endfunction "}}} 259 | 260 | function! s:open_quickfix() "{{{ 261 | let l:func = get(g:, 'ghcmod_open_quickfix_function', '') 262 | if empty(l:func) 263 | cwindow 264 | else 265 | try 266 | call call(l:func, []) 267 | catch 268 | echomsg substitute(v:exception, '^.*:[WE]\d\+: ', '', '') 269 | \ .': Please check g:ghcmod_open_quickfix_function' 270 | endtry 271 | endif 272 | endfunction "}}} 273 | 274 | " vim: set ts=2 sw=2 et fdm=marker: 275 | -------------------------------------------------------------------------------- /doc/ghcmod.txt: -------------------------------------------------------------------------------- 1 | *ghcmod.txt* Happy Haskell programming on Vim, powered by ghc-mod 2 | 3 | Author: eagletmt 4 | Repository: https://github.com/eagletmt/ghcmod-vim 5 | 6 | CONTENTS *ghcmod-contents* 7 | 8 | Features |ghcmod-features| 9 | Requirements |ghcmod-requirements| 10 | Details |ghcmod-details| 11 | Global variables |ghcmod-global-variables| 12 | Customize |ghcmod-customize| 13 | License |ghcmod-license| 14 | 15 | ============================================================================== 16 | FEATURES *ghcmod-features* 17 | 18 | - Displaying the type of sub-expressions (ghc-mod type) 19 | - Displaying the info for identifiers (ghc-mod info) 20 | - Displaying error/warning messages and their locations (ghc-mod check and 21 | ghc-mod lint) 22 | - Displaying the expansion of splices (ghc-mod expand) 23 | - Insert split function cases (ghc-mod split) 24 | 25 | Completions are supported by another plugin. See neco-ghc 26 | . 27 | 28 | ============================================================================== 29 | REQUIREMENTS *ghcmod-requirements* 30 | 31 | - vimproc 32 | - ghc-mod 33 | 34 | You can install ghc-mod via cabal-install. 35 | > 36 | cabal install ghc-mod 37 | < 38 | 39 | ============================================================================== 40 | DETAILS *ghcmod-details* 41 | 42 | If you'd like to give GHC options, set |g:ghcmod_ghc_options|. 43 | 44 | :GhcModType[!] *:GhcModType* 45 | The sub-expression under the cursor is highlighted and its type is 46 | echoed. If you type |:GhcModType| multiple times, the sub-expression 47 | changes. 48 | 49 | Sub-expressions are highlighted as |hl-Search| by default. You can 50 | customize it by setting |g:ghcmod_type_highlight|. 51 | 52 | When the current buffer is modified, this command is disabled unless 53 | "!" is given. 54 | 55 | :GhcModTypeInsert[!] *:GhcModTypeInsert* 56 | Insert a type signature under the cursor. 57 | 58 | When the current buffer is modified, this command is disabled unless 59 | "!" is given. 60 | 61 | :GhcModSplitFunCase[!] *:GhcModSplitFunCase* 62 | Insert split function cases for the variable under the cursor. 63 | Example: 64 | 65 | f :: [a] -> a 66 | f x = _body 67 | 68 | When invoked with the cursor placed on x the following will be 69 | inserted: 70 | 71 | f :: [a] -> a 72 | f [] = _body 73 | f (x:xs) = _ body 74 | 75 | When the current buffer is modified, this command is disabled unless 76 | "!" is given. 77 | 78 | :GhcModSigCodegen[!] *:GhcModSigCodegen* 79 | Insert initial code from the given signature. 80 | 81 | Example: 82 | 83 | func :: [a] -> Maybe b -> (a -> b) -> (a,b) 84 | 85 | When invoked with the cursor placed on func the following will be 86 | inserted: 87 | 88 | func x y z f = _func_body 89 | 90 | When the current buffer is modified, this command is disabled unless 91 | "!" is given. 92 | 93 | :GhcModInfo[!] [{identifier}] *:GhcModInfo* 94 | Information about {identifier} is echoed. If {identifier} isn't given, 95 | the word under the cursor is used. 96 | 97 | When the current buffer is modified, this command is disabled unless 98 | "!" is given. 99 | 100 | Information about {identifier} is echoed. 101 | 102 | :GhcModInfoPreview[!] [{identifier}] *:GhcModInfoPreview* 103 | Information about the {identifier} is displayed in the preview window. 104 | If {identifier} isn't given, the word under the cursor is used. Info 105 | accumulates until the preview window is closed or renamed, for example 106 | by a different command. 107 | 108 | When the current buffer is modified, this command is disabled unless 109 | "!" is given. 110 | 111 | :GhcModInfoPreview[!] {identifier} 112 | Information about {identifier} is displayed in the preview window. 113 | Info accumulates until the preview window is closed or renamed, 114 | for example by a different command. 115 | 116 | When the current buffer is modified, this command is disabled unless 117 | "!" is given. 118 | 119 | :GhcModTypeClear *:GhcModTypeClear* 120 | Clear the highlight created by |:GhcModType|. 121 | 122 | :GhcModCheck[!] *:GhcModCheck* 123 | Display compiler errors/warnings in |quickfix|. 124 | 125 | When the current buffer is modified, this command is disabled unless 126 | "!" is given. 127 | 128 | :GhcModLint[!] *:GhcModLint* 129 | Display hlint suggestions in |quickfix|. If you'd like to pass options 130 | to hlint, set |g:ghcmod_hlint_options|. 131 | 132 | When the current buffer is modified, this command is disabled unless 133 | "!" is given. 134 | 135 | :GhcModCheckAsync[!] *:GhcModCheckAsync* 136 | Asynchronous variant of |:GhcModCheck|. This would be useful when 137 | you'd like to run check and/or lint automatically (especially on 138 | |BufWritePost|). See |ghcmod-customize| wiki page for more detail. 139 | 140 | When the current buffer is modified, this command is disabled unless 141 | "!" is given. 142 | 143 | :GhcModLintAsync[!] *:GhcModLintAsync* 144 | Asynchronous variant of |:GhcModLint|. 145 | 146 | When the current buffer is modified, this command is disabled unless 147 | "!" is given. 148 | 149 | :GhcModCheckAndLintAsync *:GhcModCheckAndLintAsync* 150 | Perform |:GhcModCheck| and |:GhcModLint| asynchronously. 151 | 152 | :GhcModExpand[!] *:GhcModExpand* 153 | Display the expansion of splices in |quickfix|. 154 | 155 | When the current buffer is modified, this command is disabled unless 156 | "!" is given. 157 | 158 | :GhcModDiagnostics *:GhcModDiagnostics* 159 | Show diagnostics. 160 | 161 | ============================================================================== 162 | GLOBAL VARIABLES *ghcmod-global-variables* 163 | 164 | g:ghcmod_ghc_options *g:ghcmod_ghc_options* 165 | Pass these options to GHC. By default, ghcmod doesn't pass any GHC 166 | options. When ghcmod finds a Cabal directory structure, ghcmod 167 | automatically append suitable options for it. 168 | 169 | Example: passing -idir1 and -idir2 to GHC 170 | > 171 | let g:ghcmod_ghc_options = ['-idir1', '-idir2'] 172 | < 173 | 174 | b:ghcmod_ghc_options *b:ghcmod_ghc_options* 175 | Buffer-local version of |g:ghcmod_ghc_options|. When both 176 | |b:ghcmod_ghc_options| and |g:ghcmod_ghc_options| are defined, 177 | |b:ghcmod_ghc_options| is given preference and |g:ghcmod_ghc_options| 178 | is ignored. 179 | 180 | Example: adding ~/.xmonad/lib when editing XMonad configs. 181 | > 182 | autocmd BufRead,BufNewFile ~/.xmonad/* call s:add_xmonad_path() 183 | function! s:add_xmonad_path() 184 | if !exists('b:ghcmod_ghc_options') 185 | let b:ghcmod_ghc_options = [] 186 | endif 187 | call add(b:ghcmod_ghc_options, '-i' . expand('~/.xmonad/lib')) 188 | endfunction 189 | < 190 | 191 | g:ghcmod_hlint_options *g:ghcmod_hlint_options* 192 | Pass these options to hlint. By default, ghcmod doesn't pass any GHC 193 | options. 194 | 195 | Example: passing --ignore=Redundant $ to hlint 196 | > 197 | let g:ghcmod_hlint_options = ['--ignore=Redundant $'] 198 | < 199 | 200 | g:ghcmod_type_highlight *g:ghcmod_type_highlight* 201 | The highlight group used in |:GhcModType|. By default, Search group is 202 | used. See |:highlight| for details on highlighting. 203 | 204 | Example: highlighting sub-expressions with yellow background 205 | > 206 | let ghcmodType ctermbg=yellow 207 | let g:ghcmod_type_highlight = 'ghcmodType' 208 | < 209 | g:ghcmod_use_basedir *g:ghcmod_use_basedir* 210 | The directory to execute ghc-mod from. 211 | 212 | If you do not set it, ghc-mod is executed from project base directory, 213 | e.g. from directory where cabal file is placed. This can have an 214 | effect particularly on template Haskell splices that expect to find 215 | certain files in certain places. 216 | 217 | g:ghcmod_max_preview_size *g:ghcmod_max_preview_size* 218 | The maximum size in lines of that the preview window will grow to 219 | while under the control of ghcmod. Useful for preventing huge lists 220 | from taking up the screen, for example with 221 | > 222 | :GhcModInfoPreview Eq 223 | < 224 | g:ghcmod_open_quickfix_function *g:ghcmod_open_quickfix_function* 225 | The function name used for showing the |quickfix| list. 226 | If undefined or empty, the quickfix list is shown by |:cwindow|. 227 | 228 | Example: show quickfix by ctrlp.vim 229 | . 230 | > 231 | let g:ghcmod_open_quickfix_function = 'GhcModQuickFixByCtrlP' 232 | function! GhcModQuickFixByCtrlP() 233 | CtrlPQuickfix 234 | endfunction 235 | < 236 | ============================================================================== 237 | CUSTOMIZE *ghcmod-customize* 238 | 239 | See wiki page . 240 | 241 | ============================================================================== 242 | LICENSE *ghcmod-license* 243 | 244 | BSD3 License, the same license as ghc-mod. 245 | 246 | vim:tw=78:ts=8:noet:ft=help:norl: 247 | -------------------------------------------------------------------------------- /autoload/ghcmod.vim: -------------------------------------------------------------------------------- 1 | function! ghcmod#highlight_group() "{{{ 2 | return get(g:, 'ghcmod_type_highlight', 'Search') 3 | endfunction "}}} 4 | 5 | " Return the current haskell identifier 6 | function! ghcmod#getHaskellIdentifier() "{{{ 7 | let c = col ('.')-1 8 | let l = line('.') 9 | let ll = getline(l) 10 | let ll1 = strpart(ll,0,c) 11 | let ll1 = matchstr(ll1,"[a-zA-Z0-9_'.]*$") 12 | let ll2 = strpart(ll,c,strlen(ll)-c+1) 13 | let ll2 = matchstr(ll2,"^[a-zA-Z0-9_'.]*") 14 | return ll1.ll2 15 | endfunction "}}} 16 | 17 | function! ghcmod#info(fexp, path, ...) "{{{ 18 | let l:cmd = ghcmod#build_command(["-b \n", 'info', a:path, a:fexp]) 19 | let l:output = ghcmod#system(l:cmd) 20 | " Remove trailing newlines to prevent empty lines 21 | let l:output = substitute(l:output, '\n*$', '', '') 22 | return s:remove_dummy_prefix(l:output) 23 | endfunction "}}} 24 | 25 | function! ghcmod#split(line, col, path, ...) "{{{ 26 | " `ghc-mod split` is available since v5.0.0. 27 | let l:cmd = ghcmod#build_command(['split', a:path, a:line, a:col]) 28 | let l:lines = s:system('split', l:cmd) 29 | if empty(l:lines) 30 | return [] 31 | endif 32 | let l:parsed = matchlist(l:lines[0], '\(\d\+\) \(\d\+\) \(\d\+\) \(\d\+\) "\(.*\)"') 33 | if len(l:parsed) < 5 34 | return [] 35 | endif 36 | return split(l:parsed[5], '\n') 37 | endfunction "}}} 38 | 39 | function! ghcmod#sig(line, col, path, ...) "{{{ 40 | " `ghc-mod sig` is available since v5.0.0. 41 | let l:cmd = ghcmod#build_command(['sig', a:path, a:line, a:col]) 42 | let l:lines = s:system('sig', l:cmd) 43 | if len(l:lines) < 3 44 | return [] 45 | endif 46 | return [l:lines[0], l:lines[2 :]] 47 | endfunction "}}} 48 | 49 | function! ghcmod#type(line, col, path, ...) "{{{ 50 | let l:cmd = ghcmod#build_command(['type', a:path, a:line, a:col]) 51 | let l:output = ghcmod#system(l:cmd) 52 | let l:types = [] 53 | for l:line in split(l:output, '\n') 54 | let l:m = matchlist(l:line, '\(\d\+\) \(\d\+\) \(\d\+\) \(\d\+\) "\([^"]\+\)"') 55 | if !empty(l:m) 56 | call add(l:types, [map(l:m[1 : 4], 'str2nr(v:val, 10)'), l:m[5]]) 57 | endif 58 | endfor 59 | return l:types 60 | endfunction "}}} 61 | 62 | function! ghcmod#detect_module() "{{{ 63 | let l:regex = '^\C>\=\s*module\s\+\zs[A-Za-z0-9.]\+' 64 | for l:lineno in range(1, line('$')) 65 | let l:line = getline(l:lineno) 66 | let l:pos = match(l:line, l:regex) 67 | if l:pos != -1 68 | let l:synname = synIDattr(synID(l:lineno, l:pos+1, 0), 'name') 69 | if l:synname !~# 'Comment' 70 | return matchstr(l:line, l:regex) 71 | endif 72 | endif 73 | let l:lineno += 1 74 | endfor 75 | return 'Main' 76 | endfunction "}}} 77 | 78 | function! s:fix_qf_lnum_col(qf) "{{{ 79 | " ghc-mod reports dummy error message with lnum=0 and col=0. 80 | " This is not suitable for Vim, so tweak them. 81 | for l:key in ['lnum', 'col'] 82 | if get(a:qf, l:key, -1) == 0 83 | let a:qf[l:key] = 1 84 | endif 85 | endfor 86 | endfunction "}}} 87 | 88 | function! ghcmod#parse_make(lines, basedir) "{{{ 89 | " `ghc-mod check` and `ghc-mod lint` produces characters but Vim cannot 90 | " treat them correctly. Vim converts characters to in readfile(). 91 | " See also :help readfile() and :help NL-used-for-Nul. 92 | let l:qflist = [] 93 | for l:output in a:lines 94 | if empty(l:output) 95 | continue 96 | endif 97 | let l:qf = {} 98 | let l:m = matchlist(l:output, '^\(\(\f\| \)\+\):\(\d\+\):\(\d\+\):\s*\(.*\)$') 99 | if len(l:m) < 5 100 | let l:qf.bufnr = 0 101 | let l:qf.type = 'E' 102 | let l:qf.text = 'parse error in ghcmod! Could not parse the following ghc-mod output:' . l:output 103 | call add(l:qflist, l:qf) 104 | break 105 | end 106 | let [l:qf.filename, _, l:qf.lnum, l:qf.col, l:rest] = l:m[1 : 5] 107 | let l:qf.filename = ghcmod#util#join_path(a:basedir, l:qf.filename) 108 | if l:rest =~# '^Warning:' 109 | let l:qf.type = 'W' 110 | let l:rest = matchstr(l:rest, '^Warning:\s*\zs.*$') 111 | elseif l:rest =~# '^Error:' 112 | let l:qf.type = 'E' 113 | let l:rest = matchstr(l:rest, '^Error:\s*\zs.*$') 114 | else 115 | let l:qf.type = 'E' 116 | endif 117 | let l:texts = split(l:rest, '\n') 118 | if len(l:texts) > 0 119 | let l:qf.text = l:texts[0] 120 | call add(l:qflist, l:qf) 121 | for l:text in l:texts[1 :] 122 | call add(l:qflist, {'text': l:text}) 123 | endfor 124 | else 125 | let l:qf.type = 'E' 126 | call s:fix_qf_lnum_col(l:qf) 127 | let l:qf.text = 'parse error in ghcmod! Could not parse the following ghc-mod output:' 128 | call add(l:qflist, l:qf) 129 | for l:text in a:lines 130 | call add(l:qflist, {'text': l:text}) 131 | endfor 132 | break 133 | endif 134 | endfor 135 | return l:qflist 136 | endfunction "}}} 137 | 138 | function! s:build_make_command(type, path) "{{{ 139 | let l:cmd = ghcmod#build_command([a:type]) 140 | if a:type ==# 'lint' 141 | for l:hopt in get(g:, 'ghcmod_hlint_options', []) 142 | call extend(l:cmd, ['-h', l:hopt]) 143 | endfor 144 | endif 145 | call add(l:cmd, a:path) 146 | return l:cmd 147 | endfunction "}}} 148 | 149 | function! ghcmod#make(type, path) "{{{ 150 | try 151 | let l:args = s:build_make_command(a:type, a:path) 152 | return ghcmod#parse_make(s:system(a:type, l:args), b:ghcmod_basedir) 153 | catch 154 | call ghcmod#util#print_error(printf('%s %s', v:throwpoint, v:exception)) 155 | endtry 156 | endfunction "}}} 157 | 158 | function! ghcmod#async_make(type, path, callback) "{{{ 159 | let l:tmpfile = tempname() 160 | let l:args = s:build_make_command(a:type, a:path) 161 | let l:proc = s:plineopen3([{'args': l:args, 'fd': { 'stdin': '', 'stdout': l:tmpfile, 'stderr': '' }}]) 162 | let l:obj = { 163 | \ 'proc': l:proc, 164 | \ 'tmpfile': l:tmpfile, 165 | \ 'callback': a:callback, 166 | \ 'type': a:type, 167 | \ 'basedir': ghcmod#basedir(), 168 | \ } 169 | function! l:obj.on_finish(cond, status) 170 | let l:qflist = ghcmod#parse_make(readfile(self.tmpfile), self.basedir) 171 | call delete(self.tmpfile) 172 | call self.callback.on_finish(l:qflist) 173 | endfunction 174 | 175 | if !ghcmod#async#register(l:obj) 176 | call l:proc.kill(15) 177 | call l:proc.waitpid() 178 | call delete(l:tmpfile) 179 | endif 180 | endfunction "}}} 181 | 182 | function! ghcmod#expand(path) "{{{ 183 | let l:dir = fnamemodify(a:path, ':h') 184 | 185 | let l:qflist = [] 186 | let l:cmd = ghcmod#build_command(['expand', "-b '\n'", a:path]) 187 | for l:line in split(ghcmod#system(l:cmd), '\n') 188 | let l:line = s:remove_dummy_prefix(l:line) 189 | 190 | " path:line:col1-col2: message 191 | " or path:line:col: message 192 | let l:m = matchlist(l:line, '^\s*\(\(\f\| \)\+\):\(\d\+\):\(\d\+\)\%(-\(\d\+\)\)\?\%(:\s*\(.*\)\)\?$') 193 | if !empty(l:m) 194 | let l:qf = {} 195 | let [l:qf.filename, _, l:qf.lnum, l:qf.col, l:col2, l:qf.text] = l:m[1 : 6] 196 | call add(l:qflist, l:qf) 197 | if !empty(l:col2) 198 | let l:qf2 = deepcopy(l:qf) 199 | let l:qf2.col = l:col2 200 | let l:qf2.text = 'Splicing end here' 201 | call add(l:qflist, l:qf2) 202 | endif 203 | else 204 | " path:(line1,col1)-(line2,col2): message 205 | let l:m = matchlist(l:line, '^\s*\(\(\f\| \)\+\):(\(\d\+\),\(\d\+\))-(\(\d\+\),\(\d\+\))\%(:\s*\(.*\)\)\?$') 206 | if !empty(l:m) 207 | let [l:filename, _, l:lnum1, l:col1, l:lnum2, l:col2, l:text] = l:m[1 : 7] 208 | call add(l:qflist, { 'filename': l:filename, 'lnum': l:lnum1, 'col': l:col1, 'text': l:text }) 209 | call add(l:qflist, { 'filename': l:filename, 'lnum': l:lnum2, 'col': l:col2, 'text': 'Splicing end here' }) 210 | else 211 | " message 212 | let l:text = substitute(l:line, '^\s\{2\}', '', '') 213 | call add(l:qflist, { 'text': l:text }) 214 | endif 215 | endif 216 | endfor 217 | 218 | for l:qf in l:qflist 219 | if has_key(l:qf, 'filename') 220 | let l:qf.filename = ghcmod#util#join_path(l:dir, l:qf.filename) 221 | endif 222 | if has_key(l:qf, 'lnum') 223 | let l:qf.lnum = str2nr(l:qf.lnum) 224 | let l:qf.col = str2nr(l:qf.col) 225 | endif 226 | call s:fix_qf_lnum_col(l:qf) 227 | endfor 228 | return l:qflist 229 | endfunction "}}} 230 | 231 | function! s:remove_dummy_prefix(str) "{{{ 232 | return substitute(a:str, '^Dummy:0:0:Error:', '', '') 233 | endfunction "}}} 234 | 235 | function! ghcmod#add_autogen_dir(path, cmd) "{{{ 236 | " detect autogen directory 237 | let l:autogen_dir = a:path . '/autogen' 238 | if isdirectory(l:autogen_dir) 239 | call extend(a:cmd, ['-g', '-i' . l:autogen_dir, '-g', '-I' . l:autogen_dir]) 240 | let l:macros_path = l:autogen_dir . '/cabal_macros.h' 241 | if filereadable(l:macros_path) 242 | call extend(a:cmd, ['-g', '-optP-include', '-g', '-optP' . l:macros_path]) 243 | endif 244 | endif 245 | endfunction "}}} 246 | 247 | function! ghcmod#build_command(args) "{{{ 248 | let l:cmd = ['ghc-mod', '--silent'] 249 | 250 | let l:dist_top = s:find_basedir() . '/dist' 251 | let l:sandboxes = split(glob(l:dist_top . '/dist-*', 1), '\n') 252 | for l:dist_dir in [l:dist_top] + l:sandboxes 253 | let l:build_dir = l:dist_dir . '/build' 254 | if isdirectory(l:build_dir) 255 | call ghcmod#add_autogen_dir(l:build_dir, l:cmd) 256 | 257 | let l:tmps = ghcmod#util#globlist(l:build_dir . '/*/*-tmp') 258 | if !empty(l:tmps) 259 | " add *-tmp directory to include path for executable project 260 | for l:tmp in l:tmps 261 | call extend(l:cmd, ['-g', '-i' . l:tmp, '-g', '-I' . l:tmp]) 262 | endfor 263 | else 264 | " add build directory to include path for library project 265 | call extend(l:cmd, ['-g', '-i' . l:build_dir, '-g', '-I' . l:build_dir]) 266 | endif 267 | endif 268 | endfor 269 | 270 | if exists('b:ghcmod_ghc_options') 271 | let l:opts = b:ghcmod_ghc_options 272 | else 273 | let l:opts = get(g:, 'ghcmod_ghc_options', []) 274 | endif 275 | for l:opt in l:opts 276 | call extend(l:cmd, ['-g', l:opt]) 277 | endfor 278 | call extend(l:cmd, a:args) 279 | return l:cmd 280 | endfunction "}}} 281 | 282 | function! ghcmod#system(...) "{{{ 283 | let l:dir = getcwd() 284 | try 285 | lcd `=ghcmod#basedir()` 286 | let l:ret = call('vimproc#system', a:000) 287 | finally 288 | lcd `=l:dir` 289 | endtry 290 | return l:ret 291 | endfunction "}}} 292 | 293 | function! s:plineopen3(...) "{{{ 294 | let l:dir = getcwd() 295 | try 296 | lcd `=ghcmod#basedir()` 297 | let l:ret = call('vimproc#plineopen3', a:000) 298 | finally 299 | lcd `=l:dir` 300 | endtry 301 | return l:ret 302 | endfunction "}}} 303 | 304 | function! s:system(type, args) "{{{ 305 | let l:tmpfile = tempname() 306 | try 307 | let l:proc = s:plineopen3([{'args': a:args, 'fd': { 'stdin': '', 'stdout': l:tmpfile, 'stderr': '' }}]) 308 | let [l:cond, l:status] = ghcmod#util#wait(l:proc) 309 | let l:tries = 1 310 | while l:cond ==# 'run' 311 | if l:tries >= 50 312 | call l:proc.kill(15) " SIGTERM 313 | call l:proc.waitpid() 314 | throw printf('ghcmod#make: `ghc-mod %s` takes too long time!', a:type) 315 | endif 316 | sleep 100m 317 | let [l:cond, l:status] = ghcmod#util#wait(l:proc) 318 | let l:tries += 1 319 | endwhile 320 | let l:lines = readfile(l:tmpfile) 321 | return l:lines 322 | finally 323 | call delete(l:tmpfile) 324 | endtry 325 | endfunction "}}} 326 | 327 | function! ghcmod#basedir() "{{{ 328 | let l:use_basedir = get(g:, 'ghcmod_use_basedir', '') 329 | if empty(l:use_basedir) 330 | return s:find_basedir() 331 | else 332 | return l:use_basedir 333 | endif 334 | endfunction "}}} 335 | 336 | function! s:find_basedir() "{{{ 337 | " search Cabal file 338 | if !exists('b:ghcmod_basedir') 339 | " `ghc-mod root` is available since v4.0.0. 340 | let l:dir = getcwd() 341 | try 342 | lcd `=expand('%:p:h')` 343 | let b:ghcmod_basedir = 344 | \ substitute(vimproc#system(['ghc-mod', '--silent', 'root']), '\n*$', '', '') 345 | finally 346 | lcd `=l:dir` 347 | endtry 348 | endif 349 | return b:ghcmod_basedir 350 | endfunction "}}} 351 | 352 | function! ghcmod#version() "{{{ 353 | return [1, 3, 1] 354 | endfunction "}}} 355 | 356 | " vim: set ts=2 sw=2 et fdm=marker: 357 | --------------------------------------------------------------------------------