Vim + Haskell Language Server

Posted on July 2, 2022

저는 nn년 전부터 시스템 관리를 위해 Vim을 써오고 있고 한동안 순정 Vim으로 아무 생각없이 써왔습니다. 그리고 하스켈을 공부하면서 부터는 vscode를 주로 썼습니다. 간단한 편집 기능키들은 손에 익은 Vim모드로 해놓고 별 불만없이 썼습니다. 전 완전 키보드파는 아니라, 마우스가 전혀 작동하지 않는 환경은 오히려 좀 답답합니다. Vim을 하스켈의 메인 편집 툴로 쓸 생각은 안하고 있던 차에, 우연히 다른 분의 Vim 문제를 도우려 살펴 보다 보니, nn년 전에 비하면 설정이 굉장히 쉬워져서, Vim에 제대로 Haskell 셋팅을 시도하게 됐습니다. 아래는 현재2022 제가 쓰고 있는 설정들인데, 플러그인들은 계속 발전하고 있으니 최신 정보들을 확인하세요.

2023.2.6 추가
한국 빔 사용자 그룹 Korean Vim User Group 디스코드가 새로 생겼습니다. Vim을 실무에 쓰고 계신 많은 분들이, 각자의 dot파일들과 노하우도 공유하고, 질문 수준에 관계없이 친절하게들 답해주십니다. 지금은 꼭 Vim관련 된 게 아니더라도, 유용한 개발 정보들을 주고 받고 있습니다.

Neovim

vi를 쓴다 하면 대부분 vi에서 확장된 vim을 의미하지 않나요? 그리고 vim을 그래픽컬하게 쓰기 위해 GUI를 입힌 gvim, spacevim, oni등 있는데, 저는 터미널에서 가장 빠르게 부트업 되는 vi를 버릴 수가 없어 neovim을 선택했습니다. vim과의 차이점은 여러 가지가 있는데, 제가 체감하는 정도는 Lua 스크립트 내장으로 퍼포먼스가 올라간 게 가장 큰 차이점이 아닐까 합니다. 시스템 설정 파일들을 주로 만지는 분들은 vimneovim이 큰 차이가 있어 보이지 않을 수 있는데, 프로그래머가 메인 툴로 쓰려면 이제는 neovim으로 넘어가는 게 편할 것 같습니다. (케케묵은 viemacs 논쟁을 얘기 하고 싶은게 아니라, 이미 vi (vim)을 쓰시는 분들 얘기입니다.)

참고 : Learn Neovim The Practical Way

프리셋

https://github.com/rockerBOO/awesome-neovim#preconfigured-configuration
아래에 나올 설정이나, 플러그인 설정을 직접하지 않아도, 미리 잘 셋팅된 프리셋 neovim들이 여러가지 있습니다. AstroNvim, CosmicNvim, NvChad, Doom Nvim, Lunarvim 등, 고생없이 바로 깔끔한 환경을 만날 수 있는 것들이 있습니다.

전 의도적으로 pre-configured를 피한 건 아닌데, 어찌 하다보니 하나 하나 다 설치해서 쓰고 있습니다. 분명 제 셋팅보다 더 깔끔하게 돌아가는 프리셋들이 많은데, 제 손때가 묻어서 그런지 그냥 쓰게되네요. 수 백 개의 부품(plugin, 설정)들 중 조합해서 만드는 프라모델 느낌도 있습니다. 어렸을 때는 그다지 프라모델 조립같은 건 좋아하진 않았는데, 지금은 일이 손에 안잡힐 때, 잘 가지고 놉니다.

VimScript를 대체 하는 임베드 스크립트 Lua

벤치마크 결과만 보면 압도적이긴 한데, 항상 그럴지는 아직 모르겠습니다.

마우스 설정

전 완벽하게 키보드만 쓰는 스타일은 아니고, 창pane 크기 조절, 텍스트 선택 등은 마우스가 편합니다. 아마도 최소한의 마우스 기능이 지원되지 않았으면 vscode를 썼을지도 모르겠습니다. 추가로 다른 앱과 복사, 붙이기를 위해 시스템 클립 보드를 사용하도록 설정합니다.

set mouse=a " 마우스로 창 크기 조절, 탭 선택 등..
set clipboard+=unnamedplus " 시스템 클립보드 쓰기

[Vim 플러그인 설치 과정]
2023-10-31 현재는 플러그인 매니저로 Lazy를 권장하는 추세입니다. lazy 로딩으로 필요할 때 올리니, neovim 부트업 속도를 개선할 수 있습니다.

지정된 폴더에 소스를 받아 넣어놔도 되지만, 지금은 대부분 플러그인 매니저를 이용합니다. 여기선 vim-plug를 기준으로 설명합니다.

  1. 플러그인 매니저 설치는 각 매니저들의 가이드에 따라 설치하시고,

  2. 플러그인을 개발하는 github 홈페이지를 찾습니다.

  3. 설정 파일(init.vim)에서 아래와 같은 위치에 github 계정과 repository를 넣어줍니다.
    (설정 파일 위치는 :echo stdpath("config")로 찾을 수 있습니다.)

    call plug#begin()
    Plug 'junegunn/fzf'
  4. 설정 파일을 vim이 인식하도록

    :source %

    를 하거나, vim을 재시작 합니다.

  5. 그 후 아래 명령을 통해 플러그인을 설치합니다.

    :PlugInstall
  6. 플러그인에 필요한 설정들을 추가합니다. 역시 설정 파일을 vim이 인식하도록 :source %를 해주거나 재시작합니다. %는 현재 파일의 처음부터 끝까지의 내용, 즉 파일 전체를 의미합니다. 파일 전체를 읽어 설정 파일에 적용하란 의미입니다. ※ 명령행command-line에서는 다른 뜻입니다. :!echo % 하면 현재 파일명을 의미하고, 여기에 파일명 modifier중 :h를 붙이면, 현재 파일이 있는 디렉토리를 의미합니다.(:help %:h로 도움말을 볼 수 있습니다.)

  7. 설치 확인 방법
    플러그인이 설치된게 자동으로 눈에 보이는 류의 플러그인이 아닐 경우

Plugin - NerdTree

예전엔 NerdTree 외에는 대안이 없었던 것 같은데 요즘은 NeoTree를 선택하는 프리셋들이 종 종 보입니다. Lua로 개발되어 좀 더 퍼포먼스가 좋고, 폰트와 이미지 활용이 더 깔끔합니다만, 아직 폴더 즐겨 찾기가 지원 되지 않아, 아직은 NerdTree를 쓰고 있습니다.

2022.11.13
Nvim-tree로 바꿨습니다. 성능면에선 파이썬으로 만든 CHADTree가 나아 보이기도 했는데, 다른 플러그인들이 기본적으로 Nvim-tree를 지원하는 것들이 종종 있습니다. 마치 지금은 neovim 생태계에서 defacto처럼 쓰이고 있는 듯 한데, CHADTree의 간결함이 분발한다면 또 모르지요.

Plugin - Auto-session

https://github.com/rmagatti/auto-session
여러 윈도우로 나눠놓고 작업하다 :qa로 종료하고 나서, 다시 열면 원래 작업하던 상태로 돌아가기 위해 사용합니다.

Plugin - Neoterm

https://github.com/kassio/neoterm
입력 모드로 들어가서 터미널을 사용하는 것 까진 매뉴얼 없이 자연스럽게 되는데, 터미널을 벗어나는 키는 봐 둘 필요가 있습니다. 디폴트는 <Ctrl \> <Ctrl n>로 설정되어 있습니다.

Plugin - Nvim Web Devicons

https://github.com/kyazdani42/nvim-web-devicons
텍스트 환경에서 UI를 구현하니, 폰트들의 모양을 이 것 저 것 활용하는데, 이러기 위해서 적당한 Font를 설치해야 합니다.

Plugin - tabular

https://github.com/godlygeek/tabular
특정 기호를 기준으로 선택한 텍스트를 정렬할 수 있습니다.

Plugin - Markdown preview

https://github.com/iamcco/markdown-preview.nvim
마크다운 작성하는 동안 브라우저와 연동해서 미리보기를 할 수 있습니다.

Plugin - FZF

https://github.com/junegunn/fzf.vim
퍼지 검색으로 손쉽게 편집할 파일을 찾을 수 있습니다.

Plugin - telescope

https://github.com/nvim-telescope/telescope.nvim
팝업창을 띄우는 기능으로, 자체적으로도 여러 기능이 있고, 다른 플러그인과 연동해서 돌아가는 기능도 많습니다.

Plugin - fzf-hoogle

https://github.com/monkoose/fzf-hoogle.vim
Hoogle 검색 플러그인

Plugin - lualine

https://github.com/nvim-lualine/lualine.nvim
아랫쪽 상태바를 예쁘게 바꿔줍니다.

Plugin - lazygit

https://github.com/kdheepak/lazygit.nvim
쉘에서 git을 사용할 때 쓰는 tui인 lazygit을 Telescope에 얹혀서 쓸 수 있습니다.

Plugin - Iron

REPL과 소통하는 걸 도와주는 플러그인입니다.
https://github.com/hkupty/iron.nvim

팁 - ESC 이외의 키를 영문 변환키로 쓰기

vim은 modal(모드가 있는) 에디터이므로 편집 모드에서 한글 상태로 작업하다, 모드가 바뀌면 영문 상태로 가는 기능이 있으면 편리합니다. 그래서, 각 종 입력기에는 이를 위해 ESC키를 누르면 영문 상태로 바뀌는 옵션들을 가지고 있습니다. 하지만, 손가락을 경제적으로 쓰기 위해 ESC키보다, 기본 위치에서 가까운 Ctrl [를 쓰는 분들은 이 옵션의 혜택을 받지 못했습니다. 그런데, 최근에 KIME를 알게 되어, 셋팅 도중 아래와 같이 테스트하니 Ctrl [도 영문으로 바뀌도록 손쉽게 설정이 가능했습니다.

# ~/.config/kime/config.yaml
# 2023-10-31 v3.0 설정 방식으로 수정 
...
engine:
  default_category: Latin
  global_category_state: false
  global_hotkeys:
    M-C-Backslash:
      behavior: !Mode Math
      result: ConsumeIfProcessed
    S-Space:
      behavior: !Toggle
          - Hangul
          - Latin
      result: Consume
    M-C-E:
      behavior: !Mode Emoji
      result: ConsumeIfProcessed
    Esc:
      behavior: !Switch Latin
      result: Bypass
    C-OpenBracket: #---------------- 추가
      behavior: !Switch Latin
      result: Bypass
    C-Space: #---------------------- 추가
      behavior: !Switch Latin
      result: Bypass
...

다른 입력기들은 셋팅 방법을 몰라, 혹은 사용자에게 드러내 놓지 않아 풀지 못한 문제였는데, 드디어 해결했습니다. 이미 훌륭하지만, 앞으로도 굉장히 기대되는 한글 입력기입니다.

수학에서 자주 쓰이는 기호와 Greek문자들을 입력하는 모드도 있습니다.

기본 수학 모드 변환 키 Ctrl + ALt + \

수학 모드 상태에서 \alpha를 입력하면 α가 입력되고, \beta를 입력하면 β가 나오는 방식입니다.
아직 2022년 10월 현재 오피셜한 문서는 따로 없으며, 아래 링크 설명이 유일합니다.
KIME 수학 기호 입력법 - damhiya

※ 저는 한영 변환키로 Shift - Space, 무조건 영문 변환키로 Ctrl - Space를 쓰게 설정하고, 아래를 init.vim에 추가해 ESC키의 대용으로 Ctrl Space를 씁니다. 2022-11-13 : 다른 플러그인들의 기본 설정과 겹치는 부분이 있어, 지금은 Ctrl Space는 따로 설정하지 않고 쓰고 있습니다.

if has('nvim')
  tnoremap <C-Space> <Esc>  
endif

2023-10-31 추가
v3.0 이 후부터 config 파일 형식이 일부 바뀌는 breaking 변화가 있습니다.
※ 설정 파일에 오류가 있으면 아무 말 없이 디폴트 설정으로 시작하는 듯 합니다. 이렇게 되면 설정 파일을 읽었는지, 무슨 설정을 잘 못한 게 있는지 알 도리가 없습니다. 전 소리 소문없이 잠행하는 동작은 언젠가 문제를 일으킨다고 생각합니다. 다행히, 체크해 볼 방법은 마련해 뒀습니다. kime를 설치할 때 kime-check 유틸이 같이 설치되고, 이 툴로 설정 파일이 문제 없는지 확인할 수 있습니다.

Language Server Protocol Client

Neovim + HLS

가장 흔한 설정이 아래 4가지로 보입니다.

1.    Vim -> 플러그인 Coc                   ------ LSP -----> HLS
2. Neovim -> 빌트인 LSP 클라이언트          ------ LSP -----> HLS
3. Neovim -> 플러그인 LanguageClient-neovim ------ LSP -----> HLS
4. Neovim -> 플러그인 Coc                   ------ LSP -----> HLS

NeovimLSP서버와 붙이려면 많이 쓰는 방식은 빌트인 되어 있는 Native LSP Client, Vim과 호환되는 Coc 또는 LanguageClient 플러그인을 씁니다. 저는 이 중 Coc를 선택했습니다. Vim + LSP 관련 내용을 서핑으로 찾아 볼 때, 어떤 플러그인 글인가 자세히 보셔야 합니다.

빌트인 LSP

Nvim lsp autocompletion: mapping, snippets, fuzzy search

Coc

※ 2023-10-31 추가: 현재, 아직도 많이 쓰이기는 한데, 저는 빌트인으로 갈아 탔습니다.

https://github.com/neoclide/coc.nvim 이 전에는 꽤 여러개의 플러그인을 동원해서 IDE스러운 설정들을 했었던 것 같은데, 지금은 LSP 클라이언트와 각 언어에 맞는 Language Server를 띄워서 붙이면 어지간한 IDE 동작들은 다 하는 것 같습니다.

LSP 클라이언트(자동 완성audo-completion 포함) 플러그인 설치

여러가지 방법이 있는데, 여기서는 coc를 선택했습니다.

Plug 'neoclide/coc.nvim', {'branch': 'release'}

Haskell Language Server (이하 HLS)

에디터들이 LSP (Language Server Protocol)에 맞춰 요청을 보내면, 이를 받아 언어 컴파일러나 기타 툴의 도움을 받아 필요한 정보를 돌려주는 툴을 Language Server라 합니다. 하스켈 생태계에서 에디터들의 IDE스런 동작을 도와주는 여러 툴들이 있었는데, 지금은 거의 Haskell Language Server로 흡수 및 통일 된 것 같습니다.

GHC, Stack, Cabal, HLS를 설치할 때, 지금(2022) 가장 권장되는 방식은 ghcup를 이용하는 방식인 듯 합니다.

ghcup tui

실행 경로가 잘 잡혀있고 정상 동작하는지 확인하기 위해 아래로 확인

> haskell-language-server-wrapper

Coc와 HLS 연결

아래 명령어를 입력해서 Coc설정 파일을 엽니다.

:CocConfig
{
  "coc.preferences.extensionUpdateCheck": "daily",
  "languageserver": {
    "haskell": {
      "command": "haskell-language-server-wrapper",
      "args": ["--lsp"],
      "rootPatterns": [
        "stack.yaml",
        "*.cabal",
        "cabal.config",    
        "package.yaml",
        "hie.yaml"
      ],
      "filetypes": [ "hs", "lhs", "haskell" ],
      "initializationOptions": { "languageServerHaskell": {} }
    }
  },
  "suggest": {
    "disableKind": true,    
    "snippetsSupport": false    
  },
  "diagnostic": {
    "virtualText": true,
    "virtualTextCurrentLineOnly": false,    
    "virtualTextLines": 1,
    "virtualTextPrefix": " —— "
  },
  "list.height": 20,
  "codeLens.enable": true,
  "coc.preferences.enableMarkdown": true,
  "coc.preferences.jumpCommand": "tab drop"    
} 

vim도 설정 파일 위치만 다를 뿐 똑같은 절차로 동작합니다.

사용 방법

예전엔 tmuxghcid를 별도로 띄워놓고 작업하는 경우가 많았는데, 요즘은 많이들 HLS로 넘어가고 있는 것 같습니다. ghcid를 보듯 창을 띄우려면 아래 명령어를 이용합니다.

:CocDiagnostics

아직 이유를 제대로 알지 못하는 오작동 경우가 종종 있는데, 재시작으로 해결해야 하는 경우가 많습니다. 아래 명령어를 입력하거나, 단축키를 지정해 놓고 쓰는 것을 추천드립니다.

:CocRestart

init.vim에 단축키 설정

map <F10> :CocRestart<CR>

:Coc까지 입력하고 tab키를 입력하면 지원 명령들을 볼 수 있습니다.
지원하는 명령들을 목록으로 볼 수도 있습니다.

:CocList

아래 명령을 이용하여 LSP 서버에게 여러가지 명령을 전달 할 수 있습니다.

:CocCommand

정의로 점프Go to Definition 후 돌아 오기가 안될 때

2022.10.28 추가

위 두가지 이유로 Ctrl o는 적합하지 않습니다.

해결책은 tag시스템을 이용하는 방법입니다.

.config/nvim/init.vim

set tagfunc=CocTagFunc

설정을 마치고 vim을 다시 시작하면,
gd또는 Ctrl ]로 정의 위치로 점프하고, Ctrl T로 다시 돌아올 수 있습니다.

윙맨Wingman

HLS는 다양한 기능들을 플러그인 구조로 구현하고 있습니다. 이 중 타입만 작성하고 hole_을 써 놓으면 코드를 추정해 주는 Wingman이란 플러그인이 있는데, 아직은 익숙하지 않은데, 원리를 좀 더 이해한다면 강력한 툴 중 하나가 될지도 모릅니다. 윙맨 플러그인은 HLS를 설치할 때 디폴트로 설치 됩니다.

설정 오류를 고칠 단서 얻는 방법

이 소스, 저 소스들을 가져와서 실행해 보며 공부하다 보면, 버전 충돌로 인한 에러를 자주 만납니다. 작동을 제대로 하고 있는지 보는 방법 중 가장 간단한 방법은 HLS 프로세스가 떠있나 보는 방법이 있고, 아래 명령어들을 이용해 단서를 얻을 수 있습니다.

꽤 오랫동안 인터넷 문서들을 보며 리눅스 관련 툴들을 설치하곤 했는데, 그 때마다 드는 생각이 “현재 잘 작동하고 있는지, 로그는 어떻게 보는지” 알려주는 문서가 제일 좋았던 것 같습니다.

Neovim 안에서

:CocCommand workspace.showOutput
:CocOpenLog
:messages

프로젝트 루트 폴더에서(stack.yaml이 있는 폴더) 커맨드 라인에서

> haskell-language-server-wrapper
> haskell-language-server-9.0.2 (특정 버전)
> stack build --verbose

문제가 생겨 HLS가 올라오지 않을 때는 제일 먼저, Neovim에 연동되어 돌아가기 전에, standalone으로 정상 작동하는지 보는게 편합니다.

HLS가 정상적으로 올라왔는데 동작이 이상할 때 CocConfig를 열어 args에 다음을 추가하여, HLS의 로그를 볼 수 있습니다.

...
"languageserver": {
  "haskell": {
    "command": "haskell-language-server-wrapper",
    "args": ["--lsp","-d","-l","hls.log"],
...

"args": ["--lsp","-d","--logfile=hls.log"]라 둬서 정상 작동하지 않는 걸 Ailrun님 조언으로 해결했습니다. 설정을 바꿨으니 CocHLS를 재시작합니다.

:CocRestart

설정 목록

쉘에서 아래 명령어로 가능한 설정을 확인 할 수 있습니다.

> haskell-language-server-wrapper generate-default-config

2022.10 버전 1.8.0.0에서는 아래와 같이 나옵니다.

{
    "haskell": {
        "checkParents": "CheckOnSave",:
        "checkProject": true,
        "formattingProvider": "ormolu",
        "maxCompletions": 40,
        "plugin": {
            "alternateNumberFormat": {
                "globalOn": true
            },
            "callHierarchy": {
                "globalOn": true
            },
            "changeTypeSignature": {
                "globalOn": true
            },
            "class": {
                "codeActionsOn": true,
                "codeLensOn": true
            },
            "eval": {
                "config": {
                    "diff": true,
                    "exception": false
                },
                "globalOn": true
            },
            "explicit-fixity": {
                "globalOn": true
            },
            "fourmolu": {
                "config": {
                    "external": false
                }
            },
            "gadt": {
                "globalOn": true
            },
            "ghcide-code-actions-bindings": {
                "globalOn": true
            },
            "ghcide-code-actions-fill-holes": {
                "globalOn": true
            },
            "ghcide-code-actions-imports-exports": {
                "globalOn": true
            },
            "ghcide-code-actions-type-signatures": {
                "globalOn": true
            },
            "ghcide-completions": {
                "config": {
                    "autoExtendOn": true,
                    "snippetsOn": true
                },
                "globalOn": true
            },
            "ghcide-hover-and-symbols": {
                "hoverOn": true,
                "symbolsOn": true
            },
            "ghcide-type-lenses": {
                "config": {
                    "mode": "always"
                },
                "globalOn": true
            },
            "hlint": {
                "codeActionsOn": true,
                "config": {
                    "flags": []
                },
                "diagnosticsOn": true
            },
            "importLens": {
                "codeActionsOn": true,
                "codeLensOn": true
            },
            "moduleName": {
                "globalOn": true
            },
            "pragmas": {
                "codeActionsOn": true,
                "completionOn": true
            },
            "qualifyImportedNames": {
                "globalOn": true
            },
            "refineImports": {
                "codeActionsOn": true,
                "codeLensOn": true
            },
            "rename": {
                "config": {
                    "crossModule": false
                },
                "globalOn": true
            },
            "retrie": {
                "globalOn": true
            }
        }
    }
}

ABI 충돌

wrapper를 돌렸을 때, ABI 충돌이 나며 끝에 아래와 같은 메시지가 나올 때가 있습니다.

{"jsonrpc":"2.0", "method":"window/showMessage", "params": {"type": 1, "message": "Couldn't find a working/matching GHC installation. Consider installing ghc-9.2.4 via ghcup or build HLS from source."}}

ABI 충돌은 결국, 현재 쓰고 있는 GHC와, HLS를 컴파일한 GHC가 맞지 않아 생기는 오류입니다. ghcup로 싹다 지우고, cache도 지우고, 다시 깨끗한 환경에서 설치했는데도 아래와 같은 오류가 났습니다.

$ haskell-language-server-9.2.4

No compiler found, expected minor version match with ghc-9.2.4 (x86_64-tinfo6) (based on resolver setting in /home/jacoo/.stack/global-project/stack.yaml).
To install the correct GHC into /home/jacoo/.stack/programs/x86_64-linux/, try running "stack setup" or use the "--install-ghc" flag. To use your system GHC installation, run "stack config set system-ghc --global true", or use the "--system-ghc" flag.
All methods exhausted!
Content-Length: 203

위와 같이 ABI 충돌을 해결하지 못할 때, HLS를 현재 시스템이 쓰는 GHC로 컴파일해서 해결을 시도할 수 있습니다.

ghcup compile hls --version 1.8.0.0 --ghc 9.2.4

저는 위 방법으로, 컴파일을 시도했는데, 제 데비안 환경과 맞지 않는 무엇이 있는지 계속 컴파일 에러가 났습니다.
Haskell Language Server 관리자이신 Airun님의 조언으로 아래와 같이 git repository에서 바로 소스를 가져오게 바꿔 해결했습니다. 컴파일이 안됐던 이유는,
release 기준으로 빌드하면 Hackage에서 가져와서 빌드하는데, Hackage에는 version bound가 이상하게 올라갈 때가 있다고 합니다. (무언가 수작업으로 해야 하는 작업이 있어, 사람 손을 타다 보니 생기는 경우라 합니다.) (Ailrun님 감사합니다.)

ghcup compile hls --git-ref master --ghc 9.2.4

컴파일을 마치고 나면, 지원되지 않는 플러그인이 있는지 확인해야 합니다. GHC버전이 올라감에 따라 바로 적용되지 않는 플러그인들이 생깁니다. 아래 명령어로 현재 컴파일된 hls가 지원하는 플러그인 목록을 볼 수 있습니다.

> haskell-language-server-wrapper --list-plugin

HLS latest 버전 매뉴얼

Stack 버그

이제 막 셋팅한 분들은 아직 보지 않으셔도 되는 내용입니다. 같은 경우를 만나는 경우는 드물 것 같긴 한데, 혹시라도 HLS가 정상적으로 실행되지 않을 때 힌트를 얻을만한 부분이 있을까 해서 올려 놓습니다.

stack new myApp으로 새 프로젝트 폴더를 만들고, 아래와 같이 수정합니다. package.yaml 파일에 아래와 같이 dependencies에서 myApp 라이브러리를 빼고

library:
  source-dirs: src

executables:
  myApp-exe:
    main:                Main.hs
    source-dirs:         app
    ghc-options:
    - -threaded
    - -rtsopts
    - -with-rtsopts=-N
    dependencies: []
      #- myApp

Main.hs 소스에서도 import Lib를 빼면

module Main where
--import Lib
main :: IO ()
main = someFunc

아래와 같은 에러가 나며, HLS가 시작되지 않습니다.

  ghcide compiled by GHC 9.0 failed to load packages: <command line>: cannot satisfy -package
  myApp-0.1.0.0
  (use -v for more information).
  Please ensure that ghcide is compiled with the same GHC installation as the project.

뜬금 없이 HLS에 포함되어 있는 ghcide가 프로젝트와 같은 GHC 버전으로 컴파일 되지 않았다는 에러입니다. 그런데, 그 위에 메시지를 보면 패키지 로드에 실패했다는 메시지도 보입니다. 이 상태에서 stack build를 하고 나면 HLS가 정상 동작합니다.

hie-bios1stack repl을 쓰는데, stack replexecutables에 있는 파일을 볼 때 dependencies에 있는 로컬 라이브러리 컴포넌트는 빌드하지 않는다고 합니다. stack buildlibraryexecutables를 모두 빌드하는데, stack repl은 다른 동작을 합니다. stack의 버그로 볼 수 있다고 합니다. (Ailrun님, 조언 감사합니다.) 이런 이유로 Cabal이 아닌 stack을 쓸 때는 stack build를 한 번 해줘야 HLS가 정상 동작합니다.

HLS가 시작 못하는 진짜 이유는, package.yaml에 의존성이 지정되어 있는 패키지(여기 경우는 myApp)를 로드하려 하는데, 해당 패키지가 없기 때문입니다. 이 때, stack build를 하면 라이브러리가 쓰이든 안 쓰이든 모두 빌드됩니다.

만일 Main.hs를 아래와 같이 import Lib를 넣어 주면, stack build를 하지 않아도 HLS가 작동합니다.

module Main where
import Lib
main :: IO ()
main = someFunc

장황하긴 한데, HLS가 동작하려면 필요한 조건과, stack이 일괄적인 동작을 하지 않는 부분은 알아두면 도움이 되는 곳이 있을 것 같습니다.

Tip

GHCi에서 vim 키바인딩 쓰기

Vim 안에서 터미널을 열어 GHCi를 쓰다 보면, Vim 키로 편집하려 들 때가 있습니다. Vim을 쓴다고 꼭 설정해야 효율적이진 않지만, 기호에 따라 쓸만한 팁입니다.

~/.haskeline 파일에

editMode: Vi

를 넣어주면 ctrl [도 작동하고 h,j,k,l,0,a,w,b등이 작동합니다.

BASH에서 vim 키바인딩 쓰기

~/.bashrc에 아래를 추가합니다.

set -o vi

바뀐 설정을 적용하기 위해

> source ~/.bashrc

이제 쉘 커맨드 환경에서도 vim 바인딩이 먹습니다.

키 반복이 아닌, 명령어 반복

@:

클립보드 내용을 vim 명령행에 붙여 넣기

Ctrl r , +


  1. hie-bios HLS가 하스켈 프로젝트를 어떻게 빌드할지 알아야 합니다. 어떤 플래그, 패키지등이 필요한지 정보가 필요합니다. hie-bios는 현재 사용중인 빌드 시스템으로부터 이런 정보들을 가져오는 툴입니다. https://github.com/haskell/hie-bios/blob/master/README.md↩︎

Github 계정이 없는 분은 메일로 보내주세요. lionhairdino at gmail.com