SCSS 파일을 자동으로 컴파일하게 하기 – vim

scss 파일을 저장할 때 자동으로 컴파일 해주는 기능을 만들어보자. 이런 종류의 기능은 보통 vim의 autocmd나 키맵 등을 설정하여 만들 수 있다. 오토 커맨드로 등록하는 경우, 특정한 패턴의 파일을 열거나, 파일을 새로 만들거나, 저장하는 전후 등의 시점에 자동으로 수행될 수 있다.

SCSS 컴파일은 외부 컴파일러에 의존하고 있다. 따라서 vim 내에서 실행하면 해당 프로그램이 실행되는 동안 vim은 해당 프로세스가 종료되기를 기다리면서 동작을 멈추게 된다. 비록 짧은 시간이기는 하지만 이 과정이 제법 신경쓰일 수 있다.

그런데 vim8부터는 비동기로 이런 작업을 처리할 수 있는 기능이 제공된다. (따라서 vim7 이하버전에서는 실행되지 않는데, 이를 위한 처리를 통합했다.) 여기서 컴파일은 pysassc에 의존하며 이는 파이썬 pip를 이용해서 libsass를 설치하면 얻을 수 있다.


이제 vim 내에서 어떤식으로 비동기 명령을 실행하는지 알아보자.

  1. job_start() 함수를 사용하면 특정한 외부 명령을 비동기로 실행할 수 있다. 이 함수가 실행되면 자식 프로세스로 주어진 명령이 시작되고, 해당 명령과 연결되는 job 객체가 생긴다.
  2. vim 과 해당 프로세스는 채널을 통해 연결되고 job 은 시작시의 job_start() 의 옵션으로 몇 가지 이벤트에 대한 핸들러함수를 지정받을 수 있다.
  3. "close_cb" 옵션으로 지정한 함수는 해당 job의 채널을 인자로 받게된다. 따라서 채널을 통해 외부 프로세스의 표준 출력 혹은 표준 에러를 읽어올 수 있다.

먼저 SCSSCompile1 이라는 함수를 작성한다. 이 함수는 비동기 작업이 지원될 때 호출될 함수이다. 그런 다음 SCSSCompile0 함수는 비동기 지원이 되지 않는 상황에 호출될 함수로 작성한다.

SCSSCompile1 함수에서는 pysassc 를 호출하여 현재 파일을 css 파일로 컴파일하도록 한다. 그리고 채널이 닫힐 때 SCSSCompileHandler 함수가 호출되도록 한다. 이 콜백 전달은 job_start() 함수에 두 번째 인자로 사전 형식으로 지정한다. close_cb 가 채널이 닫힐 때 호출되는 콜백이다.

function! SCSSCompile1()
    echom 'Compiling: ' . expand('%')
    let l:commands = ['pysassc', expand('%'), expand('%<') . '.css']
    let job = job_start(l:commands, {'close_cb': 'SCSSCompileHandler'})
endfunction

function! SCSSCompile0()
    echo 'Compiling...'
    let cname = expand('%')
    let oname = expand('%<') . '.css'
    normal! ':!sassc ' . cname . ' ' . 'oname'
endfunction

핸들러 함수는 채널을 인자로 받으며, 채널의 상태를 확인하여 표준 출력 부분의 데이터가 남아있으면 이를 읽어서 메시지로 출력한다. 이 때 ch_status() 함수와 ch_read() 함수를 사용한다. 이는 각각 한 줄씩만 읽어들이므로 반복하여 사용해야 한다.

function! SCSSCompileHandler(ch)
    echo 'Compiliation Completed'
    while ch_status(a:ch, {'part': 'out'})
        let output = ch_read(a:ch, {'timeout': 1})
        echom output
    endwhile
endfunction

이제 두 함수중 하나를 읽어들일 명령인 BuildSCSS를 정의한다. 이 명령은 SCSSCompile{ } 로 중괄호를 포함하는 함수를 호출하는데, 중괄호 이름은 내부 표현식의 평가 결과에 의한다. 즉 has('job') 이 있으면 1, 없으면 0이기 때문에 그 때마다 적당한 명령이 실행될 것이다.

command! -nargs=0 BuildSCSS call SCSSCompile{has('job')}()

최종적으로 *.scss, *.sass 파일을 저장할 때마다 이 명령이 실행되도록 autocmd를 등록한다.

augroup PYSASS
    au!
    au BufWritePost *.sass,*.scss BuildSCSS
augroup END

이상으로 scss 파일을 편집하고 저장하면 자동으로 css 파일로 컴파일하는 과정을 구현해보았다. 아래는 전체 코드이다.

function! SCSSCompile1()
    let l:commands = ['pysassc', expand('%'), expand('%<') . '.css']
    let job = job_start(l:commands, {'close_cb': 'SCSSCompileHandler'})
endfunction

function! SCSSCompile0()
    echo 'Compiling...'
    let cname = expand('%')
    let oname = expand('%<') . '.css'
    normal! ':!sassc ' . cname . ' ' . 'oname'
endfunction

function! SCSSCompileHandler(ch)
    echo 'Compiliation Completed'
    while ch_status(a:ch, {'part': 'out'})
        let output = ch_read(a:ch, {'timeout': 1})
        echom output
    endwhile
endfunction

function! SCSSCompile1()
    echom 'Compiling: ' . expand('%')
    let l:commands = ['pysassc', expand('%'), expand('%<') . '.css']
    let job = job_start(l:commands, {'callback': 'SCSSCompileHandler'})
endfunction

function! SCSSCompile0()
    echo 'Compiling...'
    let cname = expand('%')
    let oname = expand('%<') . '.css'
    normal! ':!sassc ' . cname . ' ' . 'oname'
endfunction

command! -nargs=0 BuildSCSS call SCSSCompile{has('job')}()
augroup PYSASS
    au!
    au BufWritePost *.sass,*.scss BuildSCSS
augroup END