last updated: 2024-05-04 03:56

vi는 배우는 것이 아니라 몸으로 익히는 것이다.
vi는 단순하고 일관성이 있다.

Intro

  • 장점
    • vi의 기본 명령을 제공한다.
    • 단순하고 용량이 작아 임베디드 환경 및 원격지에서 사용하기에 좋다.
    • neatvi로 부터 여러 기능이 추가되었다.
    • vim, nvim과는 다르게 간단한 설정과 빠른 동작이 장점이다.
    • utf-8을 지원한다. 한글 입출력 가능.
    • 쉘명령 출력을 ex-mode(명령라인)에서 터미널 깨짐 없이 불러올 수 있다.
    • 각종 플러그인이 없는 대신 ex-mode에서 터미널 툴과 상호 작용할 수 있다.

개인적으로 vim보다는 nvim을 더 선호한다. lua와 결합된 플러그인과의 연결이 정말 좋기 때문이다. 하지만 단점도 많다. lua를 공부해야하고 또 설정에 적응하려면 많은 시간과 노력이 필요하다. 더군다나 수많은 플러그인을 장착하게 되면 느려지고 무거워진다. 설정이 잘 못 되면 바로 잡느라 삽질을 해야한다. 물론 자신만의 설정을 잘 만들어서 백업해 두었다가 재사용하면 좋다. 하지만 nvim 자체의 패키지 사이즈와 수 많은 플러그인의 사이즈를 생각하면 작고 가볍게라는 말이 무색해진다.

데비안을 쓰는 경우에는 더욱 문제가 많아진다. 버전이 낮아 플러그인 적용이 어려운 경우가 빈번히 발생한다. 최신 소스를 받아 컴파일하면 되지만 이것도 쉽지 않다. 컴파일 환경을 만들고 필요한 라이브러리를 준비하고 에러가 나면 이를 상황에 맞게 조정해 주어야 한다. 더군다나 라즈베리파이 같은 환경에서는 더더욱 어렵다.

이러 저러한 문제로 상황에 맞게 작고 가볍고 빠른 vi 에디터를 찾아보기 시작했다. vi의 수많은 변종과 후손들 중에 nvi를 찾았다. 소스를 가져다 컴파일해서 쓰기에도 문제가 없다. 하지만 한글이 지원되지 않는다. 즉 utf8을 지원하지 않는다는 것이다. 이를 패치한 것이 있는 지 알아보았지만 찾을 수 없었다.

다음은 vis 라는 것을 찾았다. nvim과 같이 lua를 지원하고 더욱 놀라운 것은 여러 줄을 동시에 편집할 수 있는 기능이 있다. 용량도 nvim에 비해 작고 빠르다. 한글도 지원이 된다. 한 동안 공부하면서 사용하다가 조금씩 껄끄러운 면이 드러났다. lua 버전이 최신 버전에 가깝기 때문에 luajit 기반으로 사용하는 입장에서 혼란을 준다. 더군다나 lua 플러그인도 nvim에 비해서 많이 부족하다. 필요한 것이 대부분 없다고 봐도 무방하다. 이것을 사용하는 유일한 장점은 다중 라인 편집이라고 생각한다. 전문 코딩을 하는 것이 아니기 때문에 개인적으로 그렇게 와닿지 않았다.

그 다음으로 찾은 것이 neatvi이다. 말 그대로 neat한 vi인 것이다. 정말 작고 간단하지만 상태 표시와 코드 문법의 컬러 표시가 지원된다. 165kb 정도의 사이즈이다. 편집에 필요한 기본적인 vi 명령은 모두 지원한다. ex 명령줄에 터미널 명령과 상호 작용도 가능하다. 이것은 정말 놀라운 것이다. 보통 nvim 의 기능과 플러그인에서 제공하는 것을 모두 터미널 명령과의 상호작용으로 해결할 수 있기 때문이다. 더욱 놀라운 것은 터미널의 출력결과가 vim, nvim 등과는 다르게 투명하게 아무 문제 없이 표시된다는 것이다. 즉 컬러와 한글, nerd font 등이 그대로 neatvi 에서 출력된다. ls,find,grep,awk,sed 등 수많은 쉘 명령을 ex 모드에서 조합해 쓸 수 있다. 자주 빈번하게 사용된다면 스크립트나 프로그램을 만들어서 플러그인 역할을 하게 만들면 된다.

neatvi가 왜 훌륭한가. 여러 가지가 있지만 위에서 언급한 것 이외에 기본 c 라이브러리만을 의존하고 그 어떤 의존성도 없고, 더욱이 utf8을 지원하여 다국어 표시와 입력이 가능하다는 점, 그럼에도 불구하고 작고 빠르다는 것이다. 작은 것이 아름답다라는 유닉스 철학과도 잘 부합한다. 필요한 툴은 터미널 명령어와 상호작용할 수 있다. 에디터는 파일 편집에만 집중하면 된다. 드디어 에디터 찾아 삼만리는 이제 끝을 향하고 있었다.

참고로 youtube에 neatvi에 대한 소개가 있다. 시작하기 전에 아래 링크를 통해 전체적인 내용을 살펴 보면 좋다.

https://www.youtube.com/watch?v=ZUdnHtvX4l4&pp=ugMICgJrbxABGAHKBQZuZWF0dmk%3D

neatvi의 주소는 다음과 같다.

https://github.com/aligrudi/neatvi

작고 가볍고 임베디드 환경 및 여타 조건에서 가볍게 사용할 수 있는 neatvi 를 매우 만족하다가 조금 불편하다는 생각이 들었다. 고성능, 다기능의 nvim을 사용하다 거의 아무 기능도 없는 것을 쓰게 된 후유증 같은 것이다. 예를 들어 줄 번호를 표시할 수 없다거나 워드랩, 현재 디렉토리 내의 파일검색 및 불러오기, 자동 들여쓰기, 문서 내의 단어 완성 지원 등이 그것이다. 그래서 neatvi의 faq나 다른 사람의 요구사항 등을 살펴보다가 분기된 nextvi를 알게 되었다. neatvi로 부터 분기되어 여러 아쉬운 기능을 추가한 버전이다.

이러한 이유로 nextvi 를 최종적으로 선택하여 사용하게 되었다. 물론 nvim등에 비할 바는 아니겠지만, 최소한의 기능과 성능으로 소스를 편집하거나 문서를 작성하는 데 있어 매우 훌룽하하고 본다. 이에 더하여 conf.c라는 파일에 자신이 선호하는 언어의 컬러를 설정해주고 컴파일해 사용한다면 더욱 쾌적한 환경이 된다. 물론 vi.c 파일을 편집하여 자신만의 키매핑도 추가할 수 있다. 복잡하다면 설정 변경 없이 써도 무방하다.

모든 것이 그렇듯이 장점만 있는 것은 아니다. vim 등에서의 다양한 기본 기능과 플러그인이 없다. 하지만 이를 보완할 수 있는 여러 방법들을 살펴본다.

아래 정리된 내용을 보기 전에 깃허브 소스의 README 파일이나 nextvi 홈의 README를 읽어 보자. neatvi와는 다르게 정말 많은 기능이 추가되었다. 그리고 독자적으로 변경된 것들도 있다. 개인적으로 주로 사용하는 방법을 적었으므로 그 외의 것들은 도움말을 참조해야 한다.

nextvi

자 그럼 설치부터 기본 사용법, 그리고 응용까지 정리해볼까 한다.

설치

  • 설치전에 gcc, clang, tcc 등의 컴파일러가 있어야한다.
  • 컴파일 후 vi를 적절한 위치로 옮기고 PATH에 추가한다.
$ git clone https://github.com/kyx0r/nextvi
$ cd nextvi
$ ./build.sh
$ ./vi

기본 사용법

vi의 기본 명령과 모두 호환된다.

  • 파일 열기
$ vi  # 파일이름 없이 열기 (:wq <filename> 으로 저장하고 종료)
$ vi file_name.c  # 파일이름 열기 (:wq 로 저장하고 종료)
  • 파일 저장 및 종료 (ex-mode)
:w              (저장: 또는 명령모드에서 `^k`)
:q              (나가기: 저장하지 않고)
:q!             (강제로 나가기: 문서 변경 무시)
:wq <filename>  (filename으로 저장하고 종료)
:wq             (저장하고 종료)
:wq!            (강제로 저장하고 종료)

`qq`,`zz`       (`:q!`)
  • 이동
    • h - 커서 앞으로 한 칸 이동
    • l - 커서 뒤로 한 칸 이동
    • k - 커서 위로 한 칸 이동
    • j - 커서 밑으로 한 칸 이동
    • ^ - 행의 첫 글자로 이동
    • $ - 행의 마지막 글자로 이동
    • + - 다음 행의 첫 글자로 이동
    • - - 이전 행의 첫 글자로 이동
    • { - 이전 문단 이동
    • } - 다음 문단 이동
    • gg - 문서의 처음으로 이동 (1G)
    • G - 문서의 마지막으로 이동
    • H - 화면의 맨 처음으로 이동
    • M - 화면의 중간으로 이동
    • L - 화면의 하단으로 이동
    • w - 다음 단어 이동
    • b - 이전 단어 이동
    • 번호G, :번호 - n 번째 행으로 이동
  • 입력
    • i - 커서 바로 앞에서 입력하기
    • I - 커서 줄 맨 앞에서 입력하기
    • a - 커서 바로 뒤에서 입력하기
    • A - 커서 줄 맨 뒤에서 입력하기
    • esc - 입력을 종료하고 명령모드로 나가기
  • 삭제
    • dd, d1d - 커서 행을 지우기
    • 3dd - 커서 행 포함 아래로 3 줄 지우기
    • d0 - 커서 위치에서 행의 처음까지 삭제
    • D, d$ - 커서 위치에서 행의 끝까지 삭제
  • 복사/ 붙이기
    • 3yw - 커서 위치에서 3 단어 복사
    • 3yy - 커서 위치에서 3 줄 복사
    • p,1p - 복사 또는 지운 것을 커서 기준 1번 붙이기
  • 마킹(Marking)
    • ma - 현 커서 위치를 a로 마킹
    • 'a - a 마킹한 곳으로 이동
    • '' - 이전 위치로 이동
  • undo/redo
    • u - undo
    • ^r - redo (Ctrl + r)
  • 줄 번호 표시
    • # - 줄 번호 표시
    • <space> - 줄 번호 표시 없애기 (또는 방향키 명령 hjkl)
    • 절대 줄 번호와 상대 줄 번호가 모두 표시된다.
    • # 명령으로만 표시할 수 있다. 항상 표시해서 사용할 수 없다.
    • 여러 줄을 복사나 지우기 할 때,
    • Tip. ^g(Ctrl+g) - 해당 줄 번호와 단어 수 등 확인가능
  • 숨은 문자 표시
    • V - 숨은 문자 표시/ 감추기
  • 단어 자동 완성 (auto complete)
    • ^g - 입력 모드에서 실행하면 이전까지 입력했던 단어를 기억하고 자동완성 동작.
    • 일부 문자를 적고 ^n다음, ^r이전 명령으로 선택
    • 한글도 된다. 입력 중간 중간에 ^g해 주어야 한다.

터미널 쉘(Shell)과 상호 작용

쉘(Shell) 전환

잠시 편집을 멈추고 쉘로 갔다가 다시 vi로 돌아와야 할 때가 있다. 소스를 컴파일하고 테스트를 한다던가 잠시 다른 작업을 해야 하는 경우이다. 다음 두 가지 방법이 있다. 이것은 모든 vi clone 에서 사용할 수 있는 방법이다.

  • Ex-Mode
    • :!sh 또는 :!bash - 본쉘 실행 또는 bash 쉘 실행
    • 쉘에서 필요한 작업을 한다.
    • $ exit - vi로 돌아오기
    • 자식 쉘을 하나 더 생성하는 방식이다.
  • Job Control
    • <esc>로 명령모드로 전환한다.
    • ^z - 터미널에서 해당 프로세스(vi)를 스톱하는 명령어
    • 이를 통해 vi를 실행했던 부모 쉘로 돌아간다.
    • 부모 쉘에서 필요한 작업을 한다.
    • $ fg 명령으로 vi로 돌아온다. ($ jobs로 stop된 명령어 확인 가능)
    • 부모 쉘을 사용하므로 편하지만 vi가 stop 되어 있는 지를 까먹을 때가 있다.

vi를 빠져 나가지 않고 여러 쉘 명령을 바로 사용할 수 있다. 예시 중 ff,gg 등은 빠른 파일 또는 내용을 찾기 위한 플러그인 역할을 멋지게 해 낼 수 있다. 후에 상세히 설명한다. 쉘 명령어가 출력되면 vi에서 멈춤이 일어나고 아무 키나 누르면 vi로 돌아온다.

쉘(Shell)과의 상호 작용

  • Shell Command
    • :!쉘명령어 - ex-mode에서 사용
    • :!pwd - 현재 작업 디렉토리 확인
    • :!ls - 현재 디렉토리 파일 리스트 출력
    • :!date - 시간 확인
    • :!ff <keyword> - fast find (빠른 파일 찾기 스크립트)
    • :!gg <keyword> - fast grep (빠른 파일 내용 찾기 스크립트)
    • :!luajit % - 현재 파일(루아스크립트)을 실행 테스트하기
    • Tip1. 명령모드에서 v;하면 :!를 빠르게 입력할 수 있다.
    • Tip2. 명령모드에서 vv하면 ex모드의 :이전명령를 재실행할 수 있다.
    • Tip3. ex모드에서 ^a로 다음 이전 명령을 불러와서 엔터로 재실행할 수 있다.
  • 상호 작용
    • :w \!less - 본문 내용을 터미널의 less로 보냄
    • :w \!cat > file.txt - 본문 내용을 cat으로 보내고 이를 file.txt로 저장
    • :w \!cat >> file.txt - 본문 내용을 cat으로 보내고 이를 file.txt로 덧붙임
    • :r \!date - date 명령 결과를 본문에 삽입
    • \!쉘명령의 형식이고 \!은 ex명령 내에서 shell 명령임을 표시한다.
  • !! 명령
    • 명령모드에서 !!를 사용하면 sed나 awk등으로 문자 치환을 할 수 있다.
    • ex모드의 :시작줄번호,끝줄번호!쉘명령과 같다.
    • snippet 프로그램 또는 스크립트를 만들어 스니팻 대용으로 쓸 수도 있다.
    • :1,1! ex명령이 하단에 찍힌다. 여기에 쉘명령을 넣으면 상호작용한다.
    • :1,1!date - 날짜를 해당 라인에 삽입
    • :23,23!awk '{print $1, $3}' - 23번 줄에서 1과 3 번째 단어로 대체
    • :5,5!sed 's/foo/bar/g' - 5번 줄에서 foo를 bar로 치환
    • :1,1!mysnip.sh mfront - 1번 줄에 스크립트 실행 결과를 삽입(front matter)

여러 개의 파일 편집하기

여러 개의 파일을 편집할 경우가 많다. 읽어 온 파일은 vi에서 버퍼라는 공간으로 복사된다. 사실은 버퍼를 편집하는 것인데, 버퍼간의 이동하는 법과 파일을 찾는 법 등을 설명한다.

  • 여러 파일 불러 오기
    • $ vi *.c - 쉘에서 vi로 여러 파일을 불러 올 때
    • :e filename.c - vi에서 특정 파일을 불러 올 때
    • :e ./filename.c - vi에서 특정 파일을 불러 올 때
    • :e /home/user01/filename.c - vi에서 특정 파일을 불러 올 때
    • 참고로 :e *.c 하면 좋을 것 같은데 nextvi는 지원하지 않는다.
    • 정확한 파일이름이 기억나지 않는다면 :!ls 명령으로 파일 리스트를 확인한 후 입력.
    • 파일이름이 상당히 길다면 마우스로 드래그해서 copy and paste 하는 것도 한 방법이다.
    • nnnfzf를 이용한 방법도 생각해 볼 수 있다.
    • 다만 동작 중에 nnn을 사용할 경우 파일 편집시 또 다른 vi가 동작한다.
  • fd를 이용한 선택적 파일 불러 오기
    • \ - 명령모드에서 이 명령을 수행하면 임의의 버퍼 창이 열린다.
    • :fd - ex-mode에서 fd(find) 명령을 수행한다.
    • 그러면 현재 디렉토리의 파일 리스트가 버퍼에 올라온다.
    • /<keyword> 명령이나 hjkl 등으로 원하는 파일로 이동한다.
    • ^i 또는 <tab>을 사용하여 해당 파일을 버퍼에 불러온다.
    • 주의할 것은 위 명령 사용시에 커서가 행의 맨 앞에 와야한다.
    • 그렇지 않으면 커서 이후의 문자열이 전달되어 해당 파일이 열리지 않는다.
    • 파일이 열리면 이를 편집하고 \를 통해 다른 파일을 또 불러 올 수 있다.
    • 말하자면 \nextvi의 내장 파일 검색 플러그인이다.
    • Tip) - 파일 리스트를 다른 조건으로 가져오기.
    • \로 파일 리스트로 이동
    • :fd명령으로 전에 불러온 파일 리스트를 dG로 모두 지운다.
    • 아래와 같이 c 소스파일 리스트만 불러 올 수 있다.
    • :1,1! find . -type f -name '*.c'
    • 동일하게 원하는 파일이름으로 이동하여 <tab>키로 파일을 연다.
  • 파일 버퍼 간의 이동
    • ^7 - 파일 버퍼 리스트를 볼 수 있다.
    • 버퍼 리스트의 번호를 입력하면 해당 버퍼로 이동한다.
    • :b - ex모드에서 사용하며 ^7과 같다.
    • :b번호 - ex모드에서 해당 버퍼로 바로 이동한다.
    • ^n - 명령모드에서 다음 파일 버퍼로 바로 전환한다.
    • 파일 버퍼는 기본 10개이다. :bx <버퍼수>로 재지정 가능하다.
    • 주의할 것은 vi 종료 시, 파일 버퍼가 모두 저장되어 있지 않으면 종료되지 않는다.
    • README를 찾아 보았는데 열린 버퍼를 개별적으로 닫는 방법은 아직 모른다.