Skip to content

Commit

Permalink
seito: Add --vim-config
Browse files Browse the repository at this point in the history
  • Loading branch information
sol committed Sep 17, 2024
1 parent aa2c6aa commit 83260c3
Show file tree
Hide file tree
Showing 38 changed files with 393 additions and 23 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ jobs:
ghc: system
steps:
- uses: actions/checkout@v3

- run: vim/run_tests.vim
if: runner.os == 'Linux'

- uses: hspec/setup-haskell@v1
with:
ghc-version: ${{ matrix.ghc }}
Expand Down
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,14 @@ instead:

### Vim integration

You can use `sensei` to load the result of the last test run into your quickfix
You can use `seito` to load the results of the last test run into your quickfix
list by executing `:make` in Vim.

For this to work, you can either create a `Makefile` or set `makeprg` to a
custom value.
For this to work, you can choose one out of three options:

(In both cases, `sed` is used to strip ANSI color sequences.)
1. Create a `Makefile`
2. Set `makeprg` to a custom value
3. Use [`sensei.vim`](vim/sensei.vim)

#### Option 1: Create a `Makefile`

Expand All @@ -78,6 +79,16 @@ Add the following to your Vim configuration (e.g.
:set makeprg=seito
```

#### Option 3: Use `sensei.vim`:

Add the following to your Vim configuration (e.g.
`~/.vim/after/ftplugin/haskell.vim`):

```vim
let vim_config = system('seito --vim-config')
execute 'source ' . vim_config
```

### Emacs integration

Similarly, you can use `sensei` to load the result of the last test run into an
Expand Down
4 changes: 3 additions & 1 deletion driver/seito.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import System.Environment
import Control.Monad
import qualified Data.ByteString.Lazy as L

import Paths_sensei (getDataFileName)

import Client

main :: IO ()
main = do
(success, output) <- getArgs >>= client ""
(success, output) <- getArgs >>= client getDataFileName ""
L.putStr output
unless success exitFailure
4 changes: 4 additions & 0 deletions foo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
# ghc -fno-diagnostics-show-caret -fno-show-error-context "$1" &> "$1.ghc-$(ghc --numeric-version).errors"
# ghc -fno-diagnostics-show-caret "$1" &> "$1.ghc-$(ghc --numeric-version).errors"
cabal exec -- runhaskell vim/test/assets/hspec.hs --expert --seed 0 > vim/test/assets/hspec.hs.errors
4 changes: 2 additions & 2 deletions package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ default-extensions:
- RecordWildCards
- ViewPatterns

other-extensions:
- NoFieldSelectors
data-files: vim/sensei.vim

dependencies:
- base >= 4.11 && < 5
Expand Down Expand Up @@ -62,6 +61,7 @@ executables:

seito:
source-dirs: driver
generated-other-modules: Paths_sensei
main: seito.hs

tests:
Expand Down
17 changes: 7 additions & 10 deletions sensei.cabal

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions src/Client.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import qualified Data.ByteString.Lazy as L

import HTTP (newSocket, socketName)

client :: FilePath -> [String] -> IO (Bool, L.ByteString)
client dir args = case args of
client :: (FilePath -> IO FilePath) -> FilePath -> [String] -> IO (Bool, L.ByteString)
client getDataFileName dir args = case args of
[] -> hIsTerminalDevice stdout >>= run
["--no-color"] -> run False
["--color"] -> run True
["--vim-config"] -> (,) True . fromString <$> getDataFileName "vim/sensei.vim"
_ -> do
hPutStrLn stderr $ "Usage: seito [ --color | --no-color ]"
return (False, "")
Expand Down
12 changes: 8 additions & 4 deletions test/ClientSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,21 @@ spec = do
describe "client" $ do
it "accepts --color" $ do
withSuccess $ \ dir -> do
client dir ["--color"] `shouldReturn` (True, fromString $ withColor Green "success")
client return dir ["--color"] `shouldReturn` (True, fromString $ withColor Green "success")

it "accepts --no-color" $ do
withSuccess $ \ dir -> do
client dir ["--no-color"] `shouldReturn` (True, "success")
client return dir ["--no-color"] `shouldReturn` (True, "success")

it "indicates failure" $ do
withFailure $ \ dir -> do
client dir [] `shouldReturn` (False, "failure")
client return dir [] `shouldReturn` (False, "failure")

context "when server socket is missing" $ do
it "reports error" $ do
withTempDirectory $ \ dir -> do
client dir [] `shouldReturn` (False, "could not connect to " <> fromString (socketName dir) <> "\n")
client return dir [] `shouldReturn` (False, "could not connect to " <> fromString (socketName dir) <> "\n")

context "with --vim-config" $ do
it "returns a path to Vim support files" $ do
client return undefined ["--vim-config"] `shouldReturn` (True, "vim/sensei.vim")
40 changes: 40 additions & 0 deletions vim/run_tests.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/bin/env -S vim -u NONE -S

set t_ti=
set t_te=

highlight red ctermfg=red
highlight green ctermfg=green

command! -nargs=* FAILURE echohl red | echo <args> | echohl none
command! -nargs=* SUCCESS echohl green | echo <args> | echohl none

function CheckResults()
if !empty(v:errors)
FAILURE "FAILURES:\n"
for error in v:errors
echo "\n"
FAILURE substitute(substitute(substitute(error, "^command line..script ", "", ""), " Expected ", "\n\nexpected: ", ""), " but got", "\n but got:", "")
endfor
cquit
else
SUCCESS "SUCCESS"
endif
endfunction

try
for name in glob("**/*.test.vim", v:true, v:true)
SUCCESS "running " . name
execute "source " . name
endfor
catch
FAILURE substitute(v:throwpoint, "^command line..script ", "", "")
echo "\n"
FAILURE v:exception
cquit
endtry

echo "\n"

call CheckResults()
quit
126 changes: 126 additions & 0 deletions vim/sensei.test.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
source vim/sensei.vim

function Require(actual, required)
for i in range(len(a:required))
if a:actual[i] > a:required[i]
return 1
elseif a:actual[i] < a:required[i]
return 0
endif
endfor
return 1
endfunction

function GhcVersion(name)
return matchlist(a:name, '\vghc-(\d+)\.(\d+)\.(\d+)\.errors')[1:3]
endfunction

function PopulateQuickFixList(name)
SUCCESS a:name
execute "cgetfile " . a:name
return filter(getqflist(), 'v:val.valid')
endfunction

for name in glob("vim/test/assets/lexical-error.hs.*.errors", v:true, v:true)
let errors = PopulateQuickFixList(name)
call assert_equal(1, len(errors))

let err = errors[0]
call assert_equal("vim/test/assets/lexical-error.hs", bufname(err.bufnr))
call assert_equal(1, err.lnum)
call assert_equal(11, err.col)
call assert_equal(0, err.end_lnum)
call assert_equal(0, err.end_col)
call assert_equal('e', err.type)
if Require(GhcVersion(name), [9,6])
call assert_equal(21231, err.nr)
else
call assert_equal(-1, err.nr)
endif
call assert_equal("\n lexical error in string/character literal at character '\\n'", err.text)
endfor

for name in glob("vim/test/assets/parse-error.hs.*.errors", v:true, v:true)
let errors = PopulateQuickFixList(name)
call assert_equal(1, len(errors))

let err = errors[0]
call assert_equal("vim/test/assets/parse-error.hs", bufname(err.bufnr))
call assert_equal(1, err.lnum)
call assert_equal(1, err.col)
call assert_equal(0, err.end_lnum)
call assert_equal(0, err.end_col)
call assert_equal('e', err.type)
if Require(GhcVersion(name), [9,8])
call assert_equal(25277, err.nr)
else
call assert_equal(-1, err.nr)
endif
call assert_equal("\n Parse error: module header, import declaration\n or top-level declaration expected.", err.text)
endfor

for name in glob("vim/test/assets/type-error.hs.*.errors", v:true, v:true)
let errors = PopulateQuickFixList(name)
call assert_equal(1, len(errors))

let err = errors[0]
call assert_equal("vim/test/assets/type-error.hs", bufname(err.bufnr))
call assert_equal(2, err.lnum)
call assert_equal(7, err.col)
call assert_equal(0, err.end_lnum)
call assert_equal(0, err.end_col)
call assert_equal('e', err.type)
if Require(GhcVersion(name), [9,6])
call assert_equal(83865, err.nr)
call assert_equal("\n Couldn't match type ‘Int’ with ‘[Char]’\n Expected: String\n Actual: Int", err.text)
else
call assert_equal(-1, err.nr)
call assert_equal("\n • Couldn't match type ‘Int’ with ‘[Char]’\n Expected: String\n Actual: Int\n • In the expression: 23 :: Int\n In an equation for ‘foo’: foo = 23 :: Int", err.text)
endif
endfor

for name in glob("vim/test/assets/type-signature-lacks-binding.hs.*.errors", v:true, v:true)
let errors = PopulateQuickFixList(name)
call assert_equal(2, len(errors))

let err = errors[0]
call assert_equal("vim/test/assets/type-signature-lacks-binding.hs", bufname(err.bufnr))
call assert_equal(1, err.lnum)
call assert_equal(1, err.col)
call assert_equal(0, err.end_lnum)
call assert_equal(0, err.end_col)
call assert_equal('e', err.type)
if Require(GhcVersion(name), [9,6])
call assert_equal(44432, err.nr)
else
call assert_equal(-1, err.nr)
endif
call assert_equal("\n The type signature for ‘foo’ lacks an accompanying binding", err.text)

let err = errors[1]
call assert_equal("vim/test/assets/type-signature-lacks-binding.hs", bufname(err.bufnr))
call assert_equal(4, err.lnum)
call assert_equal(1, err.col)
call assert_equal(0, err.end_lnum)
call assert_equal(0, err.end_col)
call assert_equal('e', err.type)
if Require(GhcVersion(name), [9,6])
call assert_equal(44432, err.nr)
else
call assert_equal(-1, err.nr)
endif
call assert_equal("\n The type signature for ‘bar’ lacks an accompanying binding", err.text)
endfor

let errors = PopulateQuickFixList("vim/test/assets/hspec.hs.errors")
call assert_equal(1, len(errors))

let err = errors[0]
call assert_equal("vim/test/assets/hspec.hs", bufname(err.bufnr))
call assert_equal(6, err.lnum)
call assert_equal(11, err.col)
call assert_equal(0, err.end_lnum)
call assert_equal(0, err.end_col)
call assert_equal('', err.type)
call assert_equal(-1, err.nr)
call assert_equal("", err.text)
13 changes: 13 additions & 0 deletions vim/sensei.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
set makeprg=seito

" GHC
set errorformat=%A%f:%l:%c:\ %t%*[^:]:\ [GHC-%n]
set errorformat+=%A%f:%l:%c:\ %t%*[^:]: " GHC 9.6

set errorformat^=%+C\ %.%#
set errorformat^=%Z
set errorformat^=%-G\ \ \ \ Suggested\ fix:%.%#
set errorformat^=%-G\ \ \ \ \ \ Perhaps\ you\ meant\ %.%# " GHC 9.2

" Hspec
set errorformat^=\ \ %f:%l:%c:\ .%#
6 changes: 6 additions & 0 deletions vim/test/assets/hspec.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Test.Hspec

main :: IO ()
main = hspec $ do
it "foo" $ do
"foo" `shouldBe` "bar"
14 changes: 14 additions & 0 deletions vim/test/assets/hspec.hs.errors
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

foo [✘]

Failures:

vim/test/assets/hspec.hs:6:11:
1) foo
expected: "bar"
but got: "foo"

Randomized with seed 0

Finished in 0.0001 seconds
1 example, 1 failure
1 change: 1 addition & 0 deletions vim/test/assets/lexical-error.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo = "bar
4 changes: 4 additions & 0 deletions vim/test/assets/lexical-error.hs.ghc-9.10.1.errors
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[1 of 2] Compiling Main ( vim/test/assets/lexical-error.hs, vim/test/assets/lexical-error.o )
vim/test/assets/lexical-error.hs:1:11: error: [GHC-21231]
lexical error in string/character literal at character '\n'

Loading

0 comments on commit 83260c3

Please sign in to comment.