vim에서 scss 파일을 컴파일하기

typescript나 scss 처럼 다른 언어로 컴파일하는 스크립트를 작성하고, 이 결과를 별도의 창에서 확인하는 기능을 vim에서 구현하는 방법을 알아보자. 기본적으로 vim에서는 :r 명령을 사용하면 쉘을 통해 실행한 외부 명령의 출력을 현재 버퍼로 가져오는 것을 수행할 수 있다. 그 외에도 job을 이용한 비동기 방식의 처리도 가능하고 아예 vim 내에 터미널 창을 만들어서 실행하는 방법등 여러 방법이 있다. 오늘은 이러한 방법을 사용해서 vim에서 scss를 컴파일한 결과를 보여주는 방법에 대해서 살펴보겠다.

오늘의 예제는 scss 파일을 css로 컴파일하는 예를 들었다. typescript 나 livescript를 js 파일로 컴파일하는 것도 같은 방식으로 처리할 수 있다. 우선 scss를 컴파일하는 컴파일러를 설치할 필요가 있다. 만약 파이썬이 설치돼 있다면 libsass의 파이썬 구현체를 간단하게 설치할 수 있다.

pip install libsass

성공적으로 설치가 됐다면, pysassc 라는 실행 가능한 파일이 함께 설치되어 사용할 수 있다.

컴파일해볼 원본 소스는 간단히 아래와 같다.

.-code {
	font-family: "Cascade Code", monospace;
}
.-as-inline-code {
	@extend .-code;
}

p, li {
	code {
		@extend .-as-inline-code;
	}
}
pre {
	background-color: #999;
	code {
		@extend .-as-inline-code;
	}
}

:r 명령을 사용하기

가장 간단한 방법은 앞서 소개했듯이 :r[ead] !{cmd} 명령을 사용하는 것이다. 이 명령은 외부명령인 {cmd}를 실행하고 해당 명령의 출력을 가져와서 현재 위치에 덧붙이게 된다. 따라서 새 버퍼창을 만들고 몇 가지 설정을 해준 다음, 해당 버퍼에서 이를 실행하면 된다. 참고로 외부 명령에 % 를 사용하면 현재 버퍼의 파일의 경로를 넘겨주는 것인데, # 를 대신 사용하면 바로 직전 버퍼(vim에서는 alternative file이라 부른다.)의 파일을 전달한다.

belowright new 
setfiletype css
r !pysassc #
set nomodifiable

위 명령은 다음과 같은 순서대로 실행한다.

  1. 아래 혹은 오른쪽으로 분할창을 생성하고 새 버퍼를 만든다. 이 시점에 커서는 새 윈도로 이동한다.
  2. 파일 타입을 css로 변경한다.
  3. pysassc 명령에 이전 버퍼의 파일을 이름을 넘겨서 결과를 가져온다.
  4. 파일을 수정할 수 없게 만든다.

조금 더

위 순서대로 명령만 나열해도 어느 정도 작동하는데, 이것 저것 다듬어서 좀 더 쓸만하게 만들어 보려고 한다. vimfiles 디렉토리 아래에 ftplugin 폴더를 만들고 그 속에 scss.vim 파일을 신규로 작성한 다음, 아래 내용을 입력하고 저장한다.

이렇게하면 scss 파일을 편집할 때마다 이 스크립트가 로딩되어 사용할 수 있게 된다. F5 키를 누르면 현재 편집중인 scss 파일을 컴파일한 결과가 분리된 창에 나타나게 된다.

# ftplugin/scss.vim
function! s:compile() 
	if &l:filetype != 'scss' | return | endif
	if exists('b:outbufnr') && winbufnr(b:outbufnr) > -1
		execute winbufnr(b:outbufnr) .. 'hide'
	endif
	let commands = ['pysassc', expand('%:p')->fnameescape()]
	let temp = bufnr()
	let b:outbufnr = bufadd('')
	execute 'belowright sbuffer ' .. b:outbufnr
	let b:srcbufnr = temp
	setlocal buftype=nofile nobuflisted noswapfile bufhidden=wipe
	setfiletype css
	nnoremap <buffer><silent> <Esc> <Cmd>hide<CR>
	let jobid = job_start(commands, #{
		\out_io: 'buffer',
		\out_buf: bufnr(),
		\exit_cb: {_, s -> popup_dialog((s == 0 ? 'Done' : 'Failed!'), 
                            \#{time:1000})},
	        \})
endfunction

nnoremap <buffer> <F5> <Cmd>call <SID>compile()<CR>
  1. 실행시 소스 버퍼에 버퍼-로컬 변수 b:outbufnr 를 만들고, 출력용 버퍼의 번호를 담아둘 것이다. 따라서 이 변수가 이미 정의돼 있다면 이전에 실행했다는 의미이므로, 출력용 버퍼를 표시하고 있는 창이 있을 때 이를 닫아야 한다.
  2. belowright new 를 하면 창을 생성한 즉시 새 창으로 이동해버린다. 따라서 bufadd('') 를 사용해서 빈 버퍼를 우선 만들고 그 결과를 b:outbufnr 에 저장해둔다. 그리고 출력창을 표시하려 할 때, 이미 있는 버퍼를 분할창에 표시하려면 :sbuffer 명령을 사용하면 된다. 창을 분할하여 버퍼를 표시하면 커서가 곧장 새 창으로 이동하고, “현재 버퍼”가 변경된다.
  3. buftype=nofile noswapfile nobuflised bufhidden=wipe 옵션은 결과 버퍼를 임시창처럼 사용하게 한다. (자세한 내용은 vim 도움말을 참고하라)
  4. job을 사용해서 결과를 실행한다. 컴파일러가 느리거나 오래 걸리는 작업이라도 그 사이에 vim 작업이 중단되지 않는다. 또한 컴파일러에서 line-by-line 으로 출력되는 것이 있으면 그때 마다 버퍼의 내용이 갱신될 수 있다. job을 생성할 때 콜백을 사용해서 완료 메시지 팝업이 1초간 뜨도록 했다.

일단 위 코드를 .vimrc에 삽입해 두는 것만으로도 작동은 할 것이다. .vimrc 뿐만 아니라 다른 vim 파일로 저장해두고 필요한 시점에 :so 명령을 사용해서 로드해두면된다.

흥미로운 것은 위에서 구현해 놓은 코드는 사실 결과를 출력하기 위한 외부 명령만 정해지면 그것이 무엇이든간에 결과를 새로운 결과창으로 표시할 수 있는 기능을 만들어놓은 것이라는 점이다. 따라서 버퍼에 따라서 자신을 처리할 수 있는 명령을 알고만 있다면 그걸 실행해서 출력창에서 내용을 확인할 수 있게 된다. 어떤 식으로 좀 더 일반적인 경우에 사용할 수 있도록 활용할 수 있는지 다음에 (꼭) 알아보도록 하자.

vim9script

# ftplugin/scss.vim 에 저장

def Compile()
  if &l:filetype != 'scss'
    return
  endif
  if exists('b:outbufnr') && winbufnr(b:outbufnr) > -1
    execute ':' .. winbufnr(b:outbufnr) .. 'hide'
  endif
  var cmds = ['pysassc', '--style=expanded', expand('%:p')->fnameescape()]
  var temp = bufnr()
  b:outbufnr = bufadd('')
  execute 'belowright sbuffer ' .. b:outbufnr
  b:srcbufnr = temp
  setlocal buftype=nofile nobuflisted noswapfile bufhidden=wipe
  setfiletype css
  nnoremap <buffer><silent> <Esc> <Cmd>hide<CR>
  var job_id = job_start(cmds, {
    out_io: 'buffer',
    out_buf: bufnr(),
    exit_cb: (j: job, s: number) => popup_dialog((s == 0 ? 'Done' : 'Failed'), {time: 1000})
    })
enddef

nnoremap <buffer> <F5> <ScriptCmd>Compile()<CR>

Read more

워드프레스에서 고스트로 이전

워드프레스에서 고스트로 이전

이 글을 쓰면서도 믿기 힘든 사실인데, 블로그라는 걸 처음 시작한지가 20년이 되었습니다. 이글루스에서 처음 시작했다가, SK컴즈가 인수한다고 발표함과 동시에 워드프레스로 플랫폼을 옮겼죠. 워드프레스오 옮긴 이후에는 호스팅 환경을 이리 저리 옮기긴 했지만 거의 18년 가까이 워드프레스를 사용해온 것 같습니다. 그 동안 워드프레스는 블로깅 툴에서 명실상부한 범용CMS로 발전했습니다. 사실 웬만한 홈페이지들은 이제

By sooop
띄어쓰기에 대한 생각

띄어쓰기에 대한 생각

업무 메일을 쓸 때 가장 많이 쓰는 말 중에 하나가 메일 말미에 ‘업무에 참고 부탁 드립니다.‘인데요, 어느 날부터 아웃룩에서 이 ‘부탁 드립니다’가 틀렸다고 맞춤법 지적을 하기 시작했습니다. 맞는 말은 ‘부탁드립니다’라고 붙여 쓰는 거라고. 사실 아래아한글 시절부터 이전의 MS워드까지, 워드프로세서들의 한국어 맞춤법 검사 실력은 거의 있으나 마나 한

By sooop

구글 포토에서 아이클라우드로 탈출한 후기

한 때 구글 포토가 백업 용량을 무제한으로 제공해 주겠다고해서, 구글 포토를 사용해서 사진을 백업해왔습니다. 물론 이 이야기의 결말은 저나 이 글을 읽고 있는 여러분이나 모두 알고 있습니다. 사실 AI에게 학습 시킬 이미지 데이터를 모으기 위한 것일 뿐이라거나 하는 이야기는 그 당시에도 있었습니다만, 에이 그래도 구글인데 용량은 넉넉하게 주겠지…하는 순진한

By sooop

Julia의 함수 사용팁

연산자의 함수적 표기 Julia의 연산자는 기본적으로 함수이며, 함수 호출 표기와 같은 방식으로 호출하는 것이 가능합니다. 또한 그 자체로 함수이기 때문에 filter(), map() 과 같이 함수를 인자로 받는 함수에도 연산자를 그대로 적용하는 것이 가능합니다. 특히 + 연산자는 sum() 함수와 같이 여러 인자를 받아 인자들의 합을 구할 수 있습니다. 2 + 3 # = 5 +(2,

By sooop