 |
|
난이도 : 초급 Damian Conway, Dr., CEO and Chief Trainer, Thoughtstream
원문 게재일 : 2009 년 5 월 06 일 번역 게재일 : 2009 년 8 월 18 일 Vimscript는 Vim 편집기를 변경하고 확장하기 위한 메커니즘이다. 스크립트를
사용하면 새 도구를 작성하고 일반 작업을 단순화할 수 있을 뿐만 아니라 편집기의 기존 기능까지도 재설계하여
변경할 수 있습니다. 시리즈의
첫 번째인 이 기사에서는 Vimscript 프로그래밍 언어의 기본 구성 요소인 값, 변수, 표현식, 명령문, 함수 및
명령에 대해 설명합니다. 또한 일련의 간단한 예제를 통해 기능을
직접 구현해 보면서 설명합니다.
 | Vimscript 및 이 시리즈의 정보
Vimscript는 Vim 편집기를 수정 및 확장할 수 있는 강력한 스크립트 언어이다. 이
언어를 사용하면 새 도구를 작성하고 일반 작업을 단순화할 수 있을 뿐 아니라 편집기의 기존 기능까지도
변경할 수 있다. 이 시리즈
기사에서는 독자가 Vim 편집기에 익숙하다고 간주합니다. |
|
우수한 텍스트 편집기
괜찮은 텍스트 편집기만 있었다면 Emacs는 아주 좋은 운영 체제였을 것이고 이와는
반대로 vi는 괜찮은 운영 체제만 만났다면 정말 좋은 텍스트 편집기였을 것이라는 오래된 농담이
있다. 이 농담은 Emacs가 vi에 비해 항상 우위를 점하고 있었던 전략적 장점인 내장 확장 프로그래밍
언어의 우수성을 반영한다. 실제로 Emacs 사용자가 RSI 유도 제어 코드를 참으면서 Lisp에서 즐겁게
확장을 작성하고 있다는 사실을 보면 내장 확장 언어의 장점이 대단하다는 것을 알 수 있다.
하지만 vi 프로그래머도 이제는 더 이상 Emacs의 내장 스크립트 언어를 부러워하지
않아도 된다.
즐겨 사용하고 있는 vi 편집기도 마찬가지로 스크립트가 지원되며 Emacs보다 훨씬
편리하게 사용할 수 있다.
이 시리즈의 기사에서는 vi 변형 중 오늘날 가장 유명한 프로그램인 Vim 편집기와
Vim에서 제공되는 단순하지만 매우 강력한 스크립트 언어에 대해 살펴본다. 이 첫 번째 기사에서는
Vim 스크립트의 기본 빌딩 블록 즉, 변수, 값, 표현식, 간단한 흐름 제어 등을 살펴본 후 Vim의 수많은
유틸리티 기능 중 일부 기능에 대해 설명한다.
이 기사에서는 독자가 Vim을 이미 가지고 있을 뿐만 아니라 Vim의 대화식 기능에
익숙한 것으로 간주한다. 그렇지 않은 경우에는 Vim의 고유 웹 사이트와 다양한 온라인 리소스를
참조하거나 출판된 책을 구입해서 볼 수 있으며 Vim 내에서 :help를
입력하여 입문자에게 필요한 정보를 얻을 수 있다. 참고자료에서 링크를
볼 수 있다.
별도로 언급하지 않는 한, 이 시리즈의 기사에 포함된 모든 예제에서는 Vim 버전
7.2 이상을 사용하는 것으로 간주한다. 다음과 같은 방법으로 편집기를 호출하여 사용 중인 Vim의
버전을 확인할 수 있다.
vim --version
또는 Vim 자체 내에서 :version을 입력할 수도 있다. 사용
중인 Vim의 버전이 구버전이라면 최신 릴리스로 업그레이드하는 것이 좋다. 이전 버전에서는 앞으로
살펴볼 Vimscript의 많은 기능이 지원되지 않기 때문이다. 참고자료 섹션의
링크를 사용하여 Vim을 다운로드하고 업그레이드할 수 있다.
Vimscript
Vim의 스크립트 언어인 Vimscript는 전형적인 동적 명령 언어이며 대부분의 일반
언어 기능 즉, 변수, 표현식, 제어 구조, 내장 함수, 사용자 정의 함수, 문자열, 고급 데이터 구조(목록
및 사전), 터미널 및 파일 I/O, 정규 표현식 패턴 일치, 예외 및 통합 디버거를 제공한다.
Vim 세션 내에서 다음을 입력하여 내장 도움말 시스템을 통해 Vimscript에 대한
Vim의 자체 문서를 볼 수 있다.
:help vim-script-intro
이제 다음으로 넘어가자.
Vim 스크립트 실행하기
Vim 스크립트 명령은 여러 가지 방법으로 실행할 수 있다. 가장 간단한 방법은
스크립트 명령을 파일(일반적으로 .vim 확장자 사용)에 저장한 후 Vim 세션 내에서 다음과 같이
:source 명령을 사용하여 해당 파일을 실행하는 것이다.
:source /full/path/to/the/scriptfile.vim
또는 Vim 명령행에서 콜론 뒤에 스크립트 명령을 직접 입력할 수도 있다. 예를 들면, 다음과 같다.
:call MyBackupFunc(expand('%'), { 'all':1, 'save':'recent'})
하지만 이렇게 직접 입력하는 사람은 거의 없다. 왜냐하면 입력 작업의 양을
줄이는 것이 스크립트를 이용하는 가장 큰 목적이기 때문이다. 따라서 다음과 같이 새
키보드 맵핑을 작성하여 Vim 스크립트를 호출하는 방법이 가장 일반적으로 사용된다.
:nmap ;s :source /full/path/to/the/scriptfile.vim<CR>
:nmap \b :call MyBackupFunc(expand('%'), { 'all': 1 })<CR>
이러한 명령은 대개 홈 디렉토리의 .vimrc 초기화 파일에 저장된다. 따라서
Normal 모드에서 작업 중이라면(즉, 텍스트를 삽입하는 중이 아니라면) 키 시퀀스 ;s가
지정된 스크립트 파일을 실행하고, \b 시퀀스가 MyBackupFunc()
함수(.vimrc에 정의해 놓은 함수)를 호출한다.
이 기사에서 설명하는 모든 Vimscript 예제에서는 다양한 유형의 키 맵핑을
트리거로 사용한다. 후속 기사에서는 두 가지 다른 일반적인 호출 기술 즉, Vim의 명령행에서
스크립트를 콜론 명령으로 실행하는 기술과 편집기 이벤트를 사용하여 스크립트를 자동으로
트리거하는 기술에 대해서도 설명한다.
구문 예제
Vim에는 매우 정교한 구문 강조 기능이 있다. 이 기능은 내장 :syntax enable
명령을 사용하여 설정하고 :syntax off 명령을 사용하여 해제할 수 있다.
구문 강조 기능을 전환할 때마다 10여 개의 문자를 입력하기가 귀찮은 경우에는 다음과
같은 Vimscript 행을 .vimrc 파일에 저장해 놓을 수도 있다.
Listing 1. 구문 강조 기능 전환
function! ToggleSyntax()
if exists("g:syntax_on")
syntax off
else
syntax enable
endif
endfunction
nmap <silent> ;s :call ToggleSyntax()<CR>
|
이렇게 하면 Normal 모드에서 ;s 시퀀스를 입력할 때마다
구문 강조 기능이 설정 또는 해제된다. 이 스크립트의 구성 요소를 하나하나 살펴보자.
첫 번째 코드 블록은 인수가 없는 ToggleSyntax()라는
함수를 정의하는 함수 선언이다. 이 사용자 정의 함수에서는 먼저 내장 Vim 함수인 exists()를
호출하여 문자열을 전달한다. exists() 함수는 문자열에 지정된 이름을
가진 변수(이 경우에는 전역 변수 g:syntax_on)가 정의되어 있는지 여부를
확인한다.
정의되어 있으면 if 명령문에서 syntax
off;가 실행되고, 그렇지 않은 경우에는 syntax enable이
실행된다. syntax enable은 g:syntax_on
변수를 정의하지만 syntax off는 이 변수 정의를 취소하기 때문에
ToggleSyntax() 함수를 반복적으로 호출하면 구문 강조 기능의 설정
및 해제 상태가 교대로 전환된다.
이제는 키 시퀀스(이 예제의 경우 ;s)를 설정하여
ToggleSyntax() 함수를 호출하는 작업만 남았다.
nmap <silent> ;s :call ToggleSyntax()<CR>
nmap은 "normal-mode key mapping"의
약어이다. nmap 뒤의 <silent>
옵션은 새로운 ;s 명령이 정상적으로 작업을 수행할 수 있도록 하기
위해 맵핑 중에 실행되는 명령이 화면에 표시되는 것을 차단한다. 마지막으로 다음 명령이 실행된다.
:call ToggleSyntax()<CR>
이는 리턴 값을 무시하려는 경우에 Vimscript에서 함수를 호출하는 방법을 보여 준다.
끝에 있는 <CR>는 <,
C, R 및 > 문자로
구성된 리터럴 시퀀스이다. 이 리터럴 시퀀스는 Vimscript에서 리터럴 캐리지 리턴과 같은 것으로 인식된다. 실제로
Vimscript에서는 이처럼 인쇄할 수 없는 다른 여러 가지 문자 표현이 인식된다. 예를 들어, 다음과 같은
키보드 맵핑을 작성하면 스페이스바가 Page Down 키처럼 작동한다. (이 기능은 대부분의 웹 브라우저에서 볼
수 있다.)
:nmap <Space> <PageDown>
Vim에서 :help keycodes를 입력하여 이러한 특수
기호의 전체 목록을 볼 수 있다.
ToggleSyntax()에서도 내장 syntax
명령을 직접 호출할 수 있다. 왜냐하면 Vim의 모든 내장 콜론 명령은 자동으로 Vimscript의 명령문도
되기 때문이다. 예를 들어, Vim에서 작성한 문서에 대해 가운데 맞춤이 적용된 제목을 붙이는 작업을
쉽게 수행하려는 경우 현재 행의 각 단어를 대문자로 변경하고 전체 행을 가운데로 맞춘 후 다음 행으로
이동하는 함수를 다음과 같이 작성하여 사용할 수 있다.
Listing 2. 가운데 맞춤이 적용된 제목 작성하기
function! CapitalizeCenterAndMoveDown()
s/\<./\u&/g "Built-in substitution capitalizes each word
center "Built-in center command centers entire line
+1 "Built-in relative motion (+1 line down)
endfunction
nmap <silent> \C :call CapitalizeCenterAndMoveDown()<CR>
|
Vimscript 명령문
이전 예제에서 보았듯이 Vimscript의 모든 명령문은 쉘 스크립트 또는 Python과
같이 줄 바꿈으로 끝난다. 여러 행으로 구성된 하나의 명령문을 실행해야 하는 경우에는 단일
백슬래시를 연결 기호로 사용한다. 두 행의 연결을 의미하는 백슬래시는 행의 끝에 있지 않고 아래
행의 앞쪽에 있다.
Listing 3. 백슬래시로 행 연결하기
call SetName(
\ first_name,
\ middle_initial,
\ family_name
\ )
|
수직 막대로 명령문을 구별하여 한 행에 둘 이상의 명령문을 입력할 수도 있다.
echo "Starting..." | call Phase(1) | call Phase(2) | echo "Done"
즉, Vimscript의 수직 막대는 대부분의 다른 프로그래밍 언어의 세미콜론과 동등한
기능을 수행한다. 아쉽게도 Vim에서는 세미콜론이 명령의 시작 부분에서 이미 다른 의미(구체적으로
명령의 행 범위의 일부로서 "현재 행에서 ...로"의 의미)로 사용되고 있기 때문에 명령문을 구별하는
데는 사용할 수 없다.
주석
명령문 구별자인 수직 막대는 주석에서도 중요하게 사용된다. Vimscript 주석은
큰따옴표로 시작하고 끝난다. 예를 들면, 다음과 같다.
Listing 4. Vimscript에서 주석 지정하기
if exists("g:syntax_on")
syntax off "Not 'syntax clear' (which does something else)
else
syntax enable "Not 'syntax on' (which overrides colorscheme)
endif
|
하지만 아쉽게도 Vimscript 문자열도 큰따옴표로 시작되며 주석보다 우선 순위가
높다. 따라서 다음과 같이 문자열이 예상되는 위치에서는 문자열로 해석되기 때문에 주석을 지정할
수 없다.
echo "> " "Print generic prompt
echo 명령은 하나 이상의 문자열을 기대하므로 이 행은 두 번째 문자열에 닫는
따옴표가 없음을 알리는 오류가 발생한다.
하지만 주석은 언제나 명령문의 시작 부분에 있을 수 있으므로 다음과 같이
수직 막대를 사용하여 새 명령문을 명시적으로 시작한 후 주석을 시작하게 되면 위의 문제점을
해결할 수 있다.
echo "> " |"Print generic prompt
값과 변수
Vimscript에서 변수를 할당하려면 특수 키워드 let을 사용해야 한다.
Listing 5. let 키워드 사용하기
let name = "Damian"
let height = 165
let interests = [ 'Cinema', 'Literature', 'World Domination', 101 ]
let phone = { 'cell':5551017346, 'home':5558038728, 'work':'?' }
|
큰따옴표 또는 작은따옴표를 구분 기호로 사용하여 문자열을 지정할 수 있다. 큰따옴표로
묶인 문자열은 "\n"(줄 바꿈), "\t"(탭),
"\u263A"(유니코드 웃는 얼굴) 또는 "\<ESC>"(이스케이프
문자)와 같은 특별한 "이스케이프 시퀀스"를 나타낸다. 이에 반해 작은따옴표는 구분 기호 내의
모든 내용을 리터럴 문자로 처리한다. 단, 작은따옴표가 두 번 연달아 있는 경우에는 하나의 리터럴
작은따옴표로 처리된다.
Vimscript의 값은 대개 다음 세 유형 중 하나이다.
- 스칼라: 문자열이나 숫자와 같은 단일 값이다. 예:
"Damian" 또는 165
- 목록: 대괄호로 구분되고 0부터 시작하는 묵시적 정수 인덱스를 사용하는
정렬된 값 시퀀스이다. 예:
['Cinema', 'Literature', 'World Domination', 101]
- 사전: 중괄호로 구분되고 명시적 문자열 키를 사용하는 정렬되지 않은
값 세트이다. 예:
{'cell':5551017346, 'home':5558038728, 'work':'?'}
목록이나 사전에 있는 값이 모두 같은 유형일 필요는 없으므로 문자열, 숫자,
중첩 목록 및 사전 등의 유형을 원하는 대로 혼합해서 사용할 수 있다.
값과는 달리 변수에는 기본 유형이 없다. 대신 처음에 할당된 값의 유형을 따른다. 따라서
앞 예제에서 name 및 height 변수는 이제 스칼라
유형(즉, 문자열이나 숫자만 저장할 수 있음)이고, interests는 목록 변수(즉,
목록만 저장할 수 있음)이고, phone은 사전 변수(사전만 저장할 수 있음)이다. 한번
할당된 변수 유형은 런타임 동안 영구적으로 엄격하게 유지된다.
let interests = 'unknown' " Error: variable type mismatch
기본적으로 변수의 범위는 변수가 처음 할당된 함수로 한정된다. 그렇지 않고 첫
번째 할당이 함수 외부에서 발생한 변수는 전역 변수이다. 하지만 표 1에서 설명하는 다양한 접두어를
사용하여 변수를 명시적으로 다른 범위에 속한 변수로 선언할 수도 있다.
표 1. Vimscript 변수 범위
|
접두어
|
의미
| |
g:
varname
| 변수가 전역 변수이다. | |
s:
varname
| 변수가 현재 스크립트 파일에 한정되는 로컬 변수이다. | |
w:
varname
| 변수가 현재 편집기 창에 한정되는 로컬 변수이다. | |
t:
varname
| 변수가 현재 편집기 탭에 한정되는 로컬 변수이다. | |
b:
varname
| 변수가 현재 편집기 버퍼에 한정되는 로컬 변수이다. | |
l:
varname
| 변수가 현재 함수에 한정되는 로컬 변수이다. | |
a:
varname
| 변수가 현재 함수의 매개변수이다. | |
v:
varname
| 변수가 Vim에 미리 정의되어 있는 변수이다. |
스크립트에서 다른 유형의 값 컨테이너에 액세스하는 데 사용할 수 있는
의사 변수도 Vim에서 제공된다. 표 2에서는 이러한 변수에 대한 요약 정보를 제공한다.
표 2. Vimscript 의사 변수
|
접두어
|
의미
| |
&
varname
| Vim 옵션(정의된 경우 로컬, 그렇지 않은 경우 전역) | |
&l:
varname
| 로컬 Vim 옵션 | |
&g:
varname
| 전역 Vim 옵션 | |
@
varname
| Vim 레지스터 | |
$
varname
| 환경 변수 |
"option" 의사 변수는 매우 유용하게 사용할 수 있다. 예를 들어, 다음과 같이
두 개의 키 맵을 설정하여 현재 탭 간격을 늘이거나 줄일 수 있다.
nmap <silent> ]] :let &tabstop += 1<CR>
nmap <silent> [[ :let &tabstop -= &tabstop > 1 ? 1 : 0<CR>
표현식
이전 예제의 [[ 키 맵핑에서는 다음과 같은 C 형태의
"삼원식"이 포함된 표현식을 사용하고 있다.
&tabstop > 1 ? 1 : 0
이 표현식은 키 맵이 현재 탭 간격을 정상적인 최소값인 1 이하로 축소하게 되는
것을 막아 준다. 이 예제에서 보여 주는 것처럼 Vimscript의 표현식은 대부분의 다른 현대 스크립트
언어에서 사용하는 것과 동일한 기본 연산자로 구성되어 있으며 일반적으로 동일한 구문을 사용한다. 표
3에서는 사용할 수 있는 연산자를 우선 순위별로 구분하여 보여 준다.
표 3. Vimscript 연산자 우선 순위 표
|
연산
|
연산자 구문
| 할당
숫자 더하기 및 할당 숫자 빼기 및 할당
문자열 연결 및 할당 |
let
var
=
expr
let
var
+=
expr
let
var
-=
expr
let
var
.=
expr
| | 삼원식 연산자 |
bool
?
expr-if-true
:
expr-if-false
| | 논리적 OR |
bool
||
bool
| | 논리적 AND |
bool
&&
bool
| 숫자 또는 문자열 같음
숫자 또는 문자열 같지 않음 숫자 또는 문자열 보다 큼 숫자 또는 문자열 크거나 같음
숫자 또는 문자열 보다 작음 숫자 또는 문자열 작거나 같음 |
expr
==
expr
expr
!=
expr
expr
>
expr
expr
>=
expr
expr
<
expr
expr
<=
expr
| 숫자 더하기 숫자 빼기 문자열 연결 |
num
+
num
num
-
num
str
.
str
| 숫자 곱하기 숫자 나누기 숫자 나머지 |
num
*
num
num
/
num
num
%
num
| 숫자로 변환 숫자 부정 논리적 NOT |
+
num
-
num
!
bool
| | 괄호 우선 순위 |
(
expr
)
|
논리적 연산자 주의 사항
C에서와 마찬가지로 Vimscript에서도 숫자 값 0만 부울 컨텍스트에서 false로
간주되며 0이 아닌 숫자 값(음수 포함)은 모두 true로 간주된다. 하지만 모든 논리적 및 비교
연산자는 true의 경우 값 1을 리턴한다.
문자열이 부울 값으로 사용될 경우에는 먼저 문자열이 정수로 변환된 다음 참(0이
아닌 값) 또는 거짓(0)으로 평가된다. 그리고 비어 있지 않은 문자열을 포함한 대부분의
문자열이 false로 평가된다. 아래 Listing에서는 빈 문자열을 테스트할 때 발생하는 전형적인
실수를 보여 준다.
Listing 6. 잘못된 빈 문자열 테스트
let result_string = GetResult();
if !result_string
echo "No result"
endif
|
이 테스트는 result_string에 빈 문자열이 할당된
경우 올바르게 작동하기는 하지만 result_string에 "I
am NOT an empty string"과 같은 문자열이 있는 경우에도 "No result"가
표시되는 문제점을 가지고 있다. 이 문제점은 문자열이 숫자(0)으로 변환된 후 부울
값(false)으로 변환되기 때문에 발생한다.
올바른 해결 방법은 다음과 같이 적합한 내장 함수를 사용하여 문자열이 비어 있는지
여부를 명시적으로 테스트하는 것이다.
Listing 7. 빈 문자열 테스트 방법
if empty(result_string)
echo "No result"
endif
|
비교 연산자 주의 사항
Vimscript에서 비교 연산자는 피연산자가 모두 문자열이 아닌 경우에는 항상 숫자
비교를 수행한다. 특히, 피연산자 중 하나만 문자열이고 다른 하나는 숫자인 경우에는 문자열을
숫자로 변환한 후 두 피연산자의 숫자 값을 비교한다. 이로 인해 다음과 같은 민감한 오류가 발생할 수 있다.
let ident = 'Vim'
if ident == 0 "Always true (string 'Vim' converted to number 0)
이러한 경우에는 다음과 같이 강력한 해결 방법을 사용할 수 있다.
if ident == '0' "Uses string equality if ident contains string
"but numeric equality if ident contains number
|
문자열 비교는 대개 Vim ignorecase 옵션의
로컬 설정을 따르지만 명시적으로 문자열 비교 연산자에 대/소문자 구분(#
추가) 또는 대/소문자 구분 안함(? 추가)을 표시할 수 있다.
Listing 8. 문자열 비교 연산자의 대/소문자 구분 여부 지정하기
if name ==? 'Batman' |"Equality always case insensitive
echo "I'm Batman"
elseif name <# 'ee cummings' |"Less-than always case sensitive
echo "the sky was can dy lu minous"
endif
|
"명시적으로 대/소문자 구분 여부가 지정된" 연산자를 사용하면 사용자의 변형된
옵션 설정에 영향을 받지 않고 스크립트를 안정적으로 실행할 수 있으므로 모든 문자열 비교에
이러한 연산자를 사용하는 것이 좋다.
산술 연산자 주의 사항
산술 표현식을 사용할 경우에는 Vim 버전 7.2까지 정수 산술 연산만 지원되었다는
점에 유의해야 한다. 이전 버전에서는 다음과 같이 코드를 잘못 작성하는 경우가 자주 발생했었다.
Listing 9. 정수 산술 연산 문제점
"Step through each file...
for filenum in range(filecount)
" Show progress...
echo (filenum / filecount * 100) . '% done'
" Make progress...
call process_file(filenum)
endfor
|
filenum은 항상 filecount보다
작기 때문에 정수 나누기 filenum/filecount의 결과는 항상 0이 된다. 따라서
루프의 반복마다 다음과 같은 결과가 표시된다.
Now 0% done
Vim 버전 7.2에서도 피연산자 중 하나가 명시적으로 부동소수점 숫자인 경우에는
부동소수점 산술 연산만 수행된다.
let filecount = 234
echo filecount/100 |" echoes 2
echo filecount/100.0 |" echoes 2.34
|
또 다른 변환 예제
앞에서 살펴본 구문 변환 스크립트를 사용하면 다른 유용한 도구도 쉽게 작성할
수 있다. 예를 들어, 오타나 실수가 자주 발생하는 단어 집합이 있을 경우 텍스트를 검토할 때
Vim의 일치 메커니즘을 활성화하여 문제가 있는 단어를 강조 표시하는 스크립트를 .vimrc에
추가할 수 있다.
예를 들어, 다음과 같은 단락을 Vim에 표시하는 키 맵핑(;p로
지정)을 작성할 수 있다.
It's easy to adapt the syntax-toggling
script shown earlier to create other useful tools. For example, if there is a set of words that you frequently misspell or misapply, you could add a script to your .vimrc to activate Vim's match mechanism and highlight problematic words when you're proofreading text.
해당 스크립트의 내용은 다음과 같다.
Listing 10. 실수가 잦은 단어 강조 표시하기
"Create a text highlighting style that always stands out...
highlight STANDOUT term=bold cterm=bold gui=bold
"List of troublesome words...
let s:words = [
\ "it's", "its",
\ "your", "you're",
\ "were", "we're", "where",
\ "their", "they're", "there",
\ "to", "too", "two"
\ ]
"Build a Vim command to match troublesome words...
let s:words_matcher
\ = 'match STANDOUT /\c\<\(' . join(s:words, '\|') . '\)\>/'
"Toggle word checking on or off...
function! WordCheck ()
"Toggle the flag (or set it if it doesn't yet exist)...
let w:check_words = exists('w:check_words') ? !w:check_words : 1
"Turn match mechanism on/off, according to new state of flag...
if w:check_words
exec s:words_matcher
else
match none
endif
endfunction
"Use ;p to toggle checking...
nmap <silent> ;p :call WordCheck()<CR>
|
이 스크립트에서는 w:check_words 변수를 부울 플래그로
사용하여 단어 검사를 설정 또는 해제한다. WordCheck() 함수의 첫 번째
행에서는 플래그가 있는지 여부를 확인하여 플래그가 있으면 할당 명령에 의해 변수의 부울 값이 전환된다.
let w:check_words = exists('w:check_words') ? !w:check_words : 1
w:check_words라는 변수가 아직 없으면 해당 변수가 생성되면서
값 1이 할당된다.
let w:check_words = exists('w:check_words') ? !w:check_words : 1
w: 접두어는 해당 플래그 변수가 항상 현재 창에
대한 로컬 변수임을 의미한다. 따라서 이 접두어를 사용하면 편집기 창별로 개별적으로 단어
검사를 전환할 수 있다. (이는 match 명령의 동작에도 동일하게
적용되므로 명령의 효과가 항상 현재 창의 로컬에만 반영된다.)
단어 검사는 Vim의 match 명령을 설정하여 사용할
수 있다. match를 실행하려면 먼저 텍스트 강조 표시 스펙(이 예제의
경우, STANDOUT)을 제공한 후 강조 표시할 텍스트를 지정하는 정규
표현식을 제공해야 한다. 이 경우에는 스크립트의 s:words 목록 변수에
지정된 모든 단어에 대해 OR 연산(즉, join(s:words,
'\|'))을 수행하여 정규 표현식이 생성된다. 그런 다음 대/소문자를 구분하지 않고 전체 단어가
일치하는 경우만 검색되도록 하기 위해 해당 대체 집합이 대/소문자를 구분하지 않는 단어 경계로
묶인다(\c\<\(...\)\>).
그런 다음 WordCheck() 함수는 결과 문자열을 Vim
명령으로 변환한 후 실행하여(exec s:words_matcher) 일치 기능을
설정한다. w:check_words가 해제된 경우에는 이 함수에서 match
none 명령이 대신 수행되면서 특수 일치가 비활성화된다.
Insert 모드에서 스크립트 작업하기
Vim 스크립트 작업은 Normal 모드로 제한되어 있지 않다. imap
또는 iabbrev 명령을 사용하여 텍스트를 삽입할 때 사용할 수 있는
키 맵핑이나 약어를 설정할 수 있다. 예를 들면, 다음과 같다.
imap <silent> <C-D><C-D> <C-R>=strftime("%e %b %Y")<CR>
imap <silent> <C-T><C-T> <C-R>=strftime("%l:%M %p")<CR>
.vimrc에 이러한 맵핑이 있을 경우에는 Insert 모드에서 Ctrl-D를 두 번 누르면
Vim의 내장 strftime() 함수가 호출되면서 결과 날짜가 삽입되며
이와 비슷하게 Ctrl-T를 두 번 누르면 현재 시간이 삽입된다.
이와 같은 방법으로 삽입 맵 또는 약어 기능을 사용하여 스크립트로 작성할
수 있는 모든 작업을 수행할 수 있다. 초기 <C-R>=(후속
계산 결과를 삽입하도록 지정)과 마지막 <CR>(이전 표현식을
실제로 계산하도록 지정) 사이에 적절한 Vimscript 표현식 또는 함수 호출을 넣기만 하면 된다. 여기에서는
<C-R>(Ctrl-R을 의미하는 Vim의 약어)과 <CR>(캐리지
리턴을 의미하는 Vim의 약어)이 다르다는 점에 유의해야 한다.
예를 들어, 다음과 같이 Vim의 내장 함수 getcwd()를
사용하여 현재 작업 디렉토리에 대한 약어를 작성할 수 있다.
iabbrev <silent> CWD <C-R>=getcwd()<CR>
또는 다음과 같이 텍스트 삽입 중에 Ctrl-C를 입력하여 호출할 수 있는 간단한 계산기를 포함시킬 수도 있다.
imap <silent> <C-C> <C-R>=string(eval(input("Calculate: ")))<CR>
해당 표현식은 다음과 같다.
string( eval( input("Calculate: ") ) )
먼저 내장 input() 함수를 호출하여 사용자에게 계산을
입력하도록 요청한다. 그러면 input()이 사용자의 입력을 문자열로
리턴한다. 그런 다음 입력 문자열이 내장 eval() 함수로 전달된다. 이
함수는 문자열을 Vimscript 표현식으로 계산하여 결과를 리턴한다. 그런 다음 내장 string()
함수가 숫자 결과를 문자열로 다시 변환한다. 마지막으로 키 맵핑의 <C-R>=
시퀀스가 최종 문자열을 삽입하게 된다.
복잡한 Insert 모드 스크립트
삽입 맵핑에는 이전 예제보다 훨씬 더 복잡한 스크립트가 사용될 수 있다. 그러한
경우에는 일반적으로 키 맵핑에서 호출할 수 있는 사용자 정의 함수를 사용하여 코드를 수정하는
것이 좋다.
예를 들어, 삽입 중에 수행되는 Ctrl-Y의 동작을 변경할 수 있다. 일반적으로
Insert 모드에서 Ctrl-Y는 "수직 복사"를 수행한다. 즉, 커서 바로 위에 있는 줄에서 같은 열에
있는 문자를 복사한다. 예를 들어, 다음과 같은 상황에서 Ctrl-Y는 커서 위치에 "m"을 삽입한다.
Glib jocks quiz nymph to vex dwarf
Glib jocks quiz ny_
하지만 수직 복사를 수행할 때 삽입 지점 위로 올라가면서 중간에 끼어 있는
빈 행을 무시하고 비어 있지 않은 첫 번째 행의 같은 열에 있는 문자를 복사할 수도
있다. 예를 들어, 다음과 같은 상황에서 Ctrl-Y는 바로 앞 행이 비어 있음에도 불구하고 "m"을
삽입한다.
Glib jocks quiz nymph to vex dwarf
Glib jocks quiz ny_
.vimrc 파일에 다음 코드를 삽입하여 이 동작을 수행할 수 있다.
Listing 11. 빈 행을 무시하도록 수정된 수직 복사
"Locate and return character "above" current cursor position...
function! LookUpwards()
"Locate current column and preceding line from which to copy...
let column_num = virtcol('.')
let target_pattern = '\%' . column_num . 'v.'
let target_line_num = search(target_pattern . '*\S', 'bnW')
"If target line found, return vertically copied character...
if !target_line_num
return ""
else
return matchstr(getline(target_line_num), target_pattern)
endif
endfunction
"Reimplement CTRL-Y within insert mode...
imap <silent> <C-Y> <C-R><C-R>=LookUpwards()<CR>
|
먼저 LookUpwards() 함수가 내장 virtcol()
함수를 사용하여 현재 삽입 지점이 있는 온스크린 열(또는 "가상 열")을 결정한다. '.'
인수는 현재 커서 위치의 열 번호를 지정한다.
let column_num = virtcol('.')
그런 다음 LookUpwards()는 내장 search()
함수를 사용하여 커서 위치에서부터 역방향으로 파일을 검색한다.
let target_pattern = '\%' . column_num . 'v.'
let target_line_num = search(target_pattern . '*\S', 'bnW')
search() 함수는 특수 대상 패턴(즉, \%column_numv.*\S)을
사용하여 커서 열(\%column_numv) 이후(.*)로
공백이 아닌 문자를 가지고 있는 가장 가까운 행(\S)을 찾는다. search()
함수의 두 번째 인수는 구성 문자열 bnW이다. 이 구성 문자열은
함수에게 역방향으로(backwards) 검색하지만 커서를 이동하지 말고(not) 검색도
랩핑(wrap)하지 않도록 지시한다. 검색이 성공하면 search()
함수는 해당하는 선행 행의 행 번호를 리턴하며 그렇지 않고 실패한 경우에는 0을 리턴한다.
그런 다음 if 명령문에서 삽입 위치에 복사할 문자(있는
경우)를 결정한다. 적합한 선행 행이 없는 경우에는 target_line_num에
0이 할당되므로 첫 번째 return 명령문이 실행되면서 "삽입하지 않음"을 나타내는 빈 문자열이 리턴된다.
하지만 적합한 선행 행이 있는 경우에는 두 번째 return 명령문이 실행된다. 이
명령문은 먼저 현재 편집기 버퍼에서 해당 선행 행의 사본을 가져온다.
return matchstr(getline(target_line_num), target_pattern)
그런 다음 이전 search() 호출과 일치하는 한 문자로 된 문자열을 찾아서 리턴한다.
return matchstr(getline(target_line_num), target_pattern)
이 새로운 수직 복사 동작을 LookUpwards() 내에
구현한 후에는 다음과 같이 imap을 사용하여 Insert 모드의 표준 Ctrl-Y 명령을 재정의하기만
하면 된다.
imap <silent> <C-Y> <C-R><C-R>=LookUpwards()<CR>
앞의 모든 예제에서는 <C-R>=을 사용하여 Vimscript
함수를 호출했지만 이 예제에서는 <C-R><C-R>=을 대신
사용한다. Ctrl-R을 한 번만 사용할 경우에는 후속 표현식의 결과가 직접 입력된 것처럼 삽입된다.
즉, 결과에 들어 있는 모든 특수 문자의 의미와 동작이 그대로 유지된다. 이에 반해 Ctrl-R을 두 번
사용하면 후속 처리가 필요 없는 약어 텍스트로 결과가 삽입된다.
이 예제의 경우에는 커서 위의 텍스트를 정확히 복사하는 것이 목적이므로 약어
삽입이 적합한 방법이다. 키 맵핑에서 <C-R>=을 사용했다면
이전 행의 리터럴 이스케이프 문자를 복사하는 동작이 해당 문자를 입력하는 동작과 같은 기능을
수행하게 되므로 그 즉시 편집기가 Insert 모드에서 빠져나가게 된다.
Vim의 내장 함수에 대한 정보 보기
지금까지의 예제를 보면서 알 수 있듯이 Vimscript의 우수성의 원천은 200개 이상의
내장 함수에 있다고 해도 과언이 아니다. 다음 명령을 입력하여 이들 함수에 대한 자세한 정보를 확인할 수 있다.
:help functions
또는 다음 명령을 실행하여 범주별로 분류된 유용한 목록에 액세스할 수도 있다.
:help function-list
후속 기사 소개
Vimscript는 Vim 편집기를 변경하고 확장하기 위한 메커니즘이다. 스크립트를
사용하면 새로운 도구(예: 오류 단어 강조 표시기)를 작성하고 일반 작업(탭 간격 변경, 시간 및
날짜 정보 삽입, 구문 강조 표시 기능 전환 등)을 단순하게 만들 수 있으며 기존 편집기 기능을
완전히 재설계(예: Ctrl-Y의 "선행 행 복사" 동작 개선)할 수도 있다.
대부분의 경우 새 언어를 가장 쉽게 배우는 방법은 예제를 활용하는 것이다. Vim
Tips wiki에는 샘플 Vimscript가 지속적으로 제공되고 있으며 이들 스크립트의 대부분이 유용한
도구이기도 하므로 적극 활용하면 좋은 효과를 얻을 수 있을 것이다. 수준 높은 Vim 스크립트 예제를
원하는 경우에는 Vim 스크립트 아카이브에 들어 있는 2000개 이상의 큰 프로젝트를 살펴볼 수 있다. 두
항목 모두 아래 참고자료 섹션에 해당 링크가 소개되어 있다.
Perl, Python, Ruby, PHP, Lua, Awk, Tcl 등의 쉘 언어에 익숙하다면 한편으로는
Vimscript가 매우 익숙하면서도(일반적인 방법론 및 개념의 경우) 다른 한편으로는 아주 큰 이질감을
느낄 수도 있다(특정 구문 표현법의 경우). 이러한 부조화를 극복하고 Vimscript를 자신의 것으로
만들려면 언어를 학습하고 실습하는 데 시간과 노력을 투자해야 한다. 어느 정도 익숙해진 후에는 현재
Vim의 작동 방식 중에서 개인적으로 마음에 들지 않는 부분을 본인에게 맞도록 스크립트를 작성하여
솔루션을 개선시킬 수도 있을 것이다.
이 기사에서는 Vimscript의 기본적인 변수, 값, 표현식 및 함수에 대해서만
설명했다. 이러한 몇 가지 구성 요소만으로 생성할 수 있는 "향상된 솔루션"은 아무래도
제한적일 수밖에 없다. 따라서 이후 기사에서는 데이터 구조, 흐름 제어, 사용자 정의 명령,
이벤트 구동 스크립트, Vim 모듈 빌드 및 다른 스크립트 언어를 이용한 Vim 확장과 같은 고급
Vimscript 도구 및 기술에 대해 살펴볼 것이다. 특히, 이 시리즈의 다음 기사에서는 Vimscript의
사용자 정의 함수의 기능과 이러한 함수를 사용하여 Vim을 개선할 수 있는 여러 가지 방법에 중점을
두고 설명한다.
참고자료 교육
제품 및 기술 얻기
- Vim
배포판 다운로드 페이지에서 사용자의 플랫폼에 해당하는 최신 버전의 Vim으로 업그레이드할 수 있다.
-
developerWorks에서 직접 다운로드할 수 있는 IBM
시험판 소프트웨어를 사용하여 Linux와 관련된 후속 개발 프로젝트를 구현해 볼 수 있다.
토론
-
사용자의 개인 프로파일과 사용자 정의 홈 페이지가 제공되는 My
developerWorks community에서는 관심을 가지고 있는 developerWorks의 여러 주제를 추적할 수 있으며 다른 developerWorks 사용자들과 의견을 나눌 수도 있다.
필자소개  | 
|  | Damian Conway는 오스트레일리아의 Monash University에서 교류 교수로서 컴퓨터 과학을
강의하고 있으며 국제 IT 교육 회사인 Thoughtstream의
CEO이다. 25년 이상 동안 날마다 vi를 사용하고 있으며 아마도 그는 평생 동안 이 중독에서 벗어나지
못할 것이다. |
기사에 대한 평가
 |
| 이 문서 북마킹 하기
|
|