구조적 편집

구조적 편집 (Structural Editing)

텍스트 편집기는 파일을 글자들의 줄로 다뤄요. 입력하고, 줄 단위(위/아래)나 글자 단위(왼/오른쪽)로 이동하고, 텍스트를 선택·복사·붙여넣기·삭제·교체하는 게 일반적인 기능이죠. 이 모든 작업은 글자 단위나 줄 단위로 이뤄져요.

그런데 Clojure 코드를 들여다보면 이야기가 좀 달라져요. Clojure 코드는 중첩된 폼(form)들의 집합으로 볼 수 있고, 각 중첩 단계는 시작·끝 구분자(( ... ), [ ... ], { ... } 등)를 가진 컬렉션이에요. 이런 규칙적인 구조는 나무(tree)를 이루고, 구조적 편집(structural editing)은 글자·줄 단위 대신 이런 노드/트리 단위로 코드를 다루는 새로운 연산 묶음이에요.

구조적 편집은 Lisp 계열 언어를 편집하는 오랜 역사가 있고, 특히 Emacs와 그중에서도 paredit 모드와 가장 많이 연결돼요. 하지만 이 아이디어는 Lisp이나 Emacs보다 더 일반적이에요. 실제로 구조적 편집은 Clojure를 지원하는 거의 모든 편집기에서 찾아볼 수 있거든요.

글자 단위 편집과 비슷하게, 구조적 편집에도 폼을 새로 만들고, 이동하고, 선택하고, 복사/붙여넣기하고, 삭제하는 방식이 있어요. 다만 대상이 글자나 줄이 아니라 중첩된 표현이라는 점만 다를 뿐이에요. 이 페이지는 구조적 편집의 높은 수준의 용어와 개념을 소개하는 게 목적이에요. 특정 편집기나 키 조합까지는 다루지 않아요. 왜냐하면 그 세부사항은 편집기에 따라 크게 달라지고, 대개 커스터마이즈도 가능하기 때문이에요.

처음 시작할 때는 적은 수의 명령을 외우는 데 집중해요. 아래에서 모두 다루는 이 명령들, kill(자르기), slurp forward, barf forward, splice, raise가 그것이에요. 다른 상황을 만날 때마다 그에 맞는 구조적 편집 명령을 찾아 도구 상자에 하나씩 추가하면 돼요.

출처: Clojure 공식문서

본문

균형 잡힌 폼 (Balanced forms)

구조적 편집의 일반적인 원칙은 폼이 항상 균형을 이룬 상태를 유지하도록 하는 거예요. 코드에 새 폼을 만들 때 이게 바로 드러나요. 컬렉션의 시작 구분자를 입력하면 편집기가 끝 구분자도 함께 넣어줘요. (를 입력하면 편집기는 ()를 넣고 커서를 가운데에 둬서 폼 안에서 계속 타이핑할 수 있게 해주죠. 대부분의 편집기는 시작·끝 구분자를 가로지를 때 시각적 피드백을 주고, 이 구조를 바탕으로 코드 접기/펼치기(code folding)까지 지원하기도 해요.

배우는 동안 흔히 겪게 되는 문제 하나가 있어요. 줄 편집 명령을 실수로 쓰다가 코드 구조의 균형을 깨뜨리는 경우예요. 예를 들어 여러 줄에 걸친 중첩 표현이 있을 때, 중간에서 한 줄을 지우면 왼쪽 괄호는 지워졌는데 그에 맞는 오른쪽 괄호는 다음 줄에 남아 있는 상황이 벌어지기 쉬워요. 당황하지 마세요!

이 상황을 고치는 흔한 방법 몇 가지예요.

  • Undo — 그 줄 명령을 그냥 되돌리고(undo) 대신 구조적 편집 명령을 쓰면 되는 경우가 많아요.
  • 구조적 편집을 껐다 켜기 — 일부 편집기에는 푸터나 다른 곳에 구조적 편집을 켜고 끄는 토글 버튼이 있어서 비교적 쉬워요. 어떤 편집기는 그게 더 어렵기도 해요.
  • 글자 편집 명령으로 고치기 — 매달려 있는 여는 괄호를 지워야 하는 경우처럼, 여는 괄호를 선택해서 (구조적 편집 밖에서) 글자 삭제로 지울 수 있어요.
  • 주석과 글자 편집 사용 — 닫아야 할 매달린 여는 괄호가 있다면 Clojure 주석 ;을 넣어서 )를 입력할 수 있게 한 다음, ;을 선택해 글자 편집으로 지우면 돼요.

이동과 선택 (Navigation and selection)

이동(과 대개 수정자로 함께 오는 선택)은 이미 갖고 있는 글자·줄 단위 이동·선택의 확장이에요. 다음 단어를 선택하는 것 외에도 다음 표현을 선택하거나, 포함하는 표현(트리 위쪽)을 선택해 범위를 넓히는(widen) 옵션이 있어요. 그리고 가장 편하다면 마우스로 선택하는 것을 계속 써도 된다는 점, 잊지 마세요.

자르기/붙여넣기, 일명 kill/yank

Emacs에서 텍스트 자르기는 "killing"이라고 하고 붙여넣기는 "yanking"이라고 불러요. Emacs에서 kill은 자른 텍스트 블록을 "kill ring"에 넣는데, 이건 사실상 스택이에요. 기본적으로 "yank"는 스택 맨 위의 텍스트를 붙여넣고 이를 kill ring에서 제거해요. 이런 기능을 전부 지원하는지는 편집기에 따라 다르지만, 중요한 건 키 바인딩을 볼 때 "kill"이 "자르기(cut)"를 뜻한다는 점이에요.

대부분의 구조적 편집기는 기본적으로 현 위치에서 현재 컬렉션의 끝까지 잘라내요. 편집기마다 다양한 다른 옵션도 있지만, 기본 kill과 선택만으로도 작업 대부분을 해결할 수 있어요.

Slurping과 barfing

"slurp forward" 명령은 현재 컬렉션 바로 뒤의 표현을 그 컬렉션 안으로 끌어들여 마지막 항목으로 만들어요. "barf forward"는 그 반대로, 컬렉션의 마지막 항목을 밖으로 꺼내서 컬렉션 뒤쪽에 놓아요. 컬렉션의 첫 번째 요소를 대상으로 하는 뒤쪽(backward) 변형도 있지만 훨씬 덜 쓰여요.

Splicing과 raising

자주 마주치는 시나리오 하나는 현재 컬렉션의 내용을 부모 컬렉션으로 풀어 넣고 싶은 경우예요. 이때 앞쪽 항목들을 버릴지("splice killing backwards"), 뒤쪽 항목들을 버릴지("splice killing forwards") 선택할 수 있어요. 그리고 커서가 있는 요소의 값만으로 부모를 대체하고 싶을 때도 있는데, 이걸 "raise"라고 해요.

Parinfer

몇 년 전에 Paredit의 새로운 대안으로 Parinfer가 개발됐어요. Parinfer는 들여쓰기를 중첩과 연결해서, 코드를 들여쓰기만 해도 자식 표현을 삽입할 수 있게 해줘요. 많은 편집기가 Paredit의 대안으로 Parinfer를 지원해요. 구조적 편집이 처음이라면 한번 시도해 보고 자신에게 맞는지 확인해 보세요! 일반적으로 이건 개인 취향의 문제로 보여요.

더 알아보기

아래 자료들은 paredit 명령을 배우거나 특정 편집기에서 구조적 편집을 쓰는 법을 익히는 다음 단계로 도움이 될 수 있어요. 대체로 핵심 명령 집합은 비슷하고 키 바인딩도 설정할 수 있어요. 기본 키 바인딩이 Emacs의 Paredit에서 유래한 경우가 많지만, 그에 얽매일 필요는 없어요. 커스터마이즈하는 게 흔한 일이니까요.