일러두기

VimEnter 2022

NeoVim의 gx

NeoVim에서 gx를 누르면 현재 커서가 위치한 문자열을 인식해서 적절한 애플리케이션으로 열어줍니다.

예를 들어 URL이면 브라우저로 열어줍니다.

gxNeoVim의 빌트인 플러그인으로, 따로 설정을 해주지 않아도 기본으로 등록됩니다.

이제부터는 NeoVim이라 부르면 번거로우니까 그냥 Vim이라 부르겠습니다.

아무튼 gx를 확인해보기 위해 vim의 설정 파일을 /dev/null로 지정해서 열어봅시다.1

$ vim -u /dev/null

그리고 :nnoremap gx로 확인해보면, 다음과 같이 <Plug>NetrwBrowseXgx에 매핑되어 있음을 알 수 있습니다.

/dev/null을 설정 파일로 지정한 vim 화면

이건 neovim github에 가보면 확인할 수 있습니다.

만약 설정 파일을 NONE으로 지정해주면 gx는 어떤 것에도 매핑되지 않습니다.

$ vim -u NONE

NONE을 설정 파일로 지정한 vim 화면

아무튼 평범하게 NeoVim을 실행하면 gxNetrwBrowseX를 호출하게 됩니다. 이건 커서가 지시하고 있는 문자열이 URL이면 해당 URL을 웹 브라우저에서 열어주게 됩니다.

Netrw는 찰스 캠벨이 만든 Vim을 위한 네트워크 상의 파일 탐색기라 할 수 있다는 정도로 알아둬도 충분합니다.

https://www.vim.org를 입력하고 커서를 놓은 다음 gx를 누르면 웹 브라우저에서 웹 사이트가 열리는 것을 확인할 수 있습니다.

gx를 확장하자

저는 vim-plug를 사용해 Vim 플러그인들을 관리하고 있습니다.

vim-plug를 사용하시는 분들이라면 Plug 명령을 알고 계실 것입니다. 예를 들어 다음과 같이 사용할 플러그인을 정의할 수 있죠.

Plug 'johngrib/vim-game-code-break'

저는 이렇게 정의한 Plug 구문에 커서를 두고 gx를 입력했을 때 웹 브라우저가 열리고 https://github.com/johngrib/vim-game-code-break로 이동하면 편리할 거라는 생각을 했습니다.

그래서 다음과 같이 gx를 확장할 계획을 세웠습니다.

  1. 정규식으로 커서가 지시하는 문자열을 확보하는 함수를 만든다.
  2. 필요한 기능에 맞춰 정규식과 처리 함수로 이루어진 배열을 만든다.
  3. gx를 입력하면 배열을 순회하며 정규식으로 커서 지시 문자열을 확인해서 일치하면 처리 함수를 호출하고 종료한다.
  4. 만약 어느 정규식에도 일치하지 않는다면 NetrwBrowseX를 호출한다.

그래서 만든 것이 vim-gx-on-regex.vim 파일입니다.

코드 설명

문자열 확보 함수

다음은 주어진 정규식을 받아서 커서가 지시하는 문자열을 확보하는 함수입니다.

function! MatchedString(regex_string) abort
    let l:cursor_x = col('.') - 1
    let l:current_line = getline('.')
    let l:target_index = -1

    let l:matched_head = match(l:current_line, a:regex_string, 0)

    if l:matched_head < 0
        return v:null
    endif

    while l:matched_head <= l:cursor_x
        if l:matched_head < 0
            return v:null
        endif

        let l:matched_tail = matchend(l:current_line, a:regex_string, l:matched_head)

        if (l:cursor_x < l:matched_head) || (l:matched_tail <= l:cursor_x)
            " 커서가 찾아낸 문자열의 범위 밖에 있으면, matched_tail 이후부터 다시 찾는다
            let l:matched_head = match(l:current_line, a:regex_string, l:matched_tail)
            continue
        endif

        " 커서가 찾아낸 문자열의 범위 안에 있으면
        let l:target_index = match(l:current_line, a:regex_string, l:matched_head)
        let l:size = l:matched_tail - l:target_index

        if l:target_index >= 0
            return strpart(l:current_line, l:target_index, l:size)
        endif

        return v:null
    endwhile

    return v:null
endfunction

같은 라인 안에서도 정규식에 매치되는 문자열이 여럿 있을 수 있기 때문에 x 좌표를 확인하기 위한 목적으로 간단한 while 루프를 돌고 있습니다.

정규식과 처리 함수

그리고 다음은 정규식과 처리 함수 배열입니다. PlugFilePlug를 인식하는 정규식이 보입니다.

let s:match_config = []
call add(s:match_config, {'regex': "\\vPlugFile +'([^ ]+)'", 'exec': {p -> s:plug_file_match(p)} })
call add(s:match_config, {'regex': "\\vPlug +'([^ /]+/[^ /]+)'", 'exec': {p -> s:plug_simple_match(p)} })

PlugFile은 제가 간단히 만든 파일 단위 vim 플러그인을 꽂아넣기 위한 기능인데요, 이번 발표의 범위는 아니니 넘어가도록 하겠습니다.2

gx에 매핑할 NetrwBrowseX 대체 함수

여기에서 주목할 것은 셋째 줄입니다. Plug 구문을 인식해서 s:plug_simple_match 함수를 호출하도록 해 두었습니다.

function! s:plug_simple_match(text)
    let l:address = 'https://github.com/' . substitute(a:text, "\\v^Plug +'([^ ']+)'$", '\1', '')
    call system('open ' .l:address)
endfunction

이 함수는 system 함수를 호출해서 open 명령을 실행합니다. open은 macOS에서 제공하는 명령으로, 주어진 URL을 브라우저로 열어줍니다.

function! s:openWhatever()
    " s:match_config 를 순회하면서 regex_string 에 매칭되는 문자열을 찾는다
    for l:config in s:match_config
        let l:matched_string = MatchedString(l:config.regex)

        if l:matched_string == v:null
            continue
        endif

        " 매칭되는 문자열이 있으면, 해당 문자열을 command 에 넣어서 실행한다
        try
            call l:config.exec(l:matched_string)
            return
        catch
            echom 'error'
        endtry
    endfor
    echom "netrw"
    " 참고: https://github.com/neovim/neovim/blob/1a9d2a4040f3ba3fe272488a7e85db6cdb453d39/runtime/plugin/netrwPlugin.vim#L84
    call netrw#BrowseX(netrw#GX(),netrw#CheckIfRemote(netrw#GX()))
endfunction

let g:netrw_nogx = v:true
nnoremap gx <cmd>call <SID>openWhatever()<CR>

그리고 gx에 배열을 루프하며 정규식을 검사해서 함수를 호출해주는 s:openWhatever를 연결해 줬습니다.

이제 이걸 사용해서 Plug 구문에서 gx하면 브라우저가 열리는 것을 확인할 수 있습니다.

주석

  1. vim -u filename 형식으로 Vim 초기화에 사용할 설정 파일을 지정할 수 있습니다. 자세한 내용은 man vim 또는 :help -u, :help initialization를 참고하세요. 

  2. [[/vim/configure-split#방법-4-plugfile-명령으로-include-파일을-명시해준다]] 문서를 참고하세요.