오픈 소스 텍스트 에디터 Lite XL 플러그인 제작 기여
오픈 소스 텍스트 에디터인 Lite XL(lite-xl.com) 프로젝트에 기여했습니다. 이번엔 플러그인을 하나 개발했는데, 자동화 프로그래밍에 높은 생산성을 보이는 AutoHotkey 언어의 하이라이팅 플러그인입니다. 코드를 짜기 위한 텍스트 에디터는 여러 언어를 지원하는 것이 생명입니다. AutoHotkey는 이미 SciTE4AutoHotkey(autohotkey.com)라는 걸출한 에디터가 있고 사용률도 높지만, Lite XL은 Lite XL만의 장점이 있습니다.
Lite XL 에디터
Lite XL 에디터는 과거 Lite라는 프로젝트에서 포크되어 갈라져 나왔습니다. 정확히는 계승되었다고 할 수 있죠. Lite 프로젝트는 2020년 이후로 릴리즈가 없고, 그 이후엔 Lite XL로 업데이트 되고 있습니다. 기성 텍스트 에디터는 마이크로소프트의 VSCode가 꽉 잡고 있고, '탈마이크로소프트'를 원한다면 VSCodium(vscodium.com)을 사용할 수 있습니다. Lite XL은 주류와는 떨어져 있는 선택지이지만 소수의 매니아층이 존재하는 듯 합니다.
저 역시 색다른 텍스트 에디터를 써보고 싶어서 Lite XL을 사용했던 적이 있습니다. 그 이후 VSCodium을 발견해서 이를 사용하고 있지만, 과도기적(?)인 경험으로는 불편하지만 매력있는 에디터였던 것 같습니다. 특출난 기능은 없지만 유난히 가벼웠던 기억이 남습니다.
Lite XL 에디터는 Lua로 제작되어 있습니다. 사용 경험이 많은 SciTE4AutoHotkey도 Lua로 제작되어 있었는데, SciTE4AutoHotkey의 유틸리티를 간소하게 하나 제작해보면서 Lua 언어의 기초적인 문법은 눈에 익은 상태였습니다. 다행히 큰 무리 없이 플러그인을 제작할 수 있었습니다.
언어 지원 플러그인 개발
구문 하이라이팅을 위해서 이미 구현되어있는 다른 언어 플러그인의 구조를 보았습니다.
local syntax = require "core.syntax"
위 구문을 통해 syntax 인스턴스를 하나 만든 후, syntax.add() 함수의 매개변수로 언어명, 파일 확장자, 하이라이팅을 적용할 패턴, 하이라이팅 할 문자열(심볼)을 테이블로 넣어 전달해주면 됩니다. Lua에서는 딕셔너리의 역할을 테이블이 합니다. 인수로 전달해야하는 테이블의 모습은 아래와 같습니다.
{
name = "언어 이름",
files = { "파일 확장자 목록 패턴 (예: %.cpp$)" },
comment = "주석 기호(예: #)",
patterns = {
패턴 테이블과 타입, 예:
{ pattern = { ";", "" }, type = "comment" },
{ pattern = { "/%*", "*%/" }, type = "comment" },
{ pattern = { "[ruU]?%%", "[%% ]", '' }, type = "operator" },
...
},
symbols = 키워드 목록 테이블 (table["키워드"] = "키워드 종류" 형식)
}
복잡해보이지만, 익숙한 딕셔너리 형식으로 바꿔서 생각해보면 단순히 name, files, comment, patterns, symbols 키에 각각의 값을 넣어서 하나의 딕셔너리로 만든 것 뿐입니다.
가장 큰 문제는 심볼 테이블입니다. AutoHotkey는 Case-insensitive한 언어입니다. 코어 모듈의 구조가 바뀌지 않는 이상 대소문자까지 정확히 일치하는 심볼에 대해서만 구문 강조가 되기 때문에 복잡해집니다. 모든 대소문자 조합에 대해 이런 식으로 키워드를 넣어줄 수는 없는 노릇입니다. 가장 간단한 MsgBox
키워드는 64가지의 경우의 수가 있습니다. 10글자짜리 키워드라면 1024개의 경우의 수를 모두 테이블에 담아주어야합니다.
그래서, 모두 대문자인 경우와 모두 소문자인 경우, 첫글자만 대문자인 경우, 그리고 AutoHotkey에서 가장 대중적인 표기법(파스칼 표기)까지 총 네 가지만 지원하기로 했습니다. AutoHotkey에서 구문 강조가 들어가야하는 단어는 크게 내장 변수, 키워드, 명령어입니다. 이 구문을 모두 배열에 넣어서, 그 배열을 반복하여 심볼 테이블에 넣어줍니다.
local keywords = {
"Break", "ByRef", "Case", "Catch", "Class",
"Continue", "Else", "Else", "Exit", "ExitApp",
"Finally", "For", "Global", "Gosub", "Goto",
"If", "Local", "Loop", "OnExit", "Pause",
"Return", "Sleep", "static", "suspend", "Switch",
"Throw", "Try", "Until", "While",
}
for _, elementKeyword in ipairs(keywords) do
symbols[string.lower(elementKeyword)] = "function" --byref
symbols[string.upper(elementKeyword)] = "function" --BYREF
symbols[string.format("%s%s", string.sub(elementKeyword, 1, 1), string.lower(string.sub(elementKeyword, 2)))] = "function" --Byref
symbols[elementKeyword] = "function" --ByRef
end
위는 AutoHotkey에서 키워드 분류로 들어가는 구문의 심볼 테이블을 만드는 방법입니다. --
뒤는 주석입니다. 만약 Case-sensitive한 언어였다면 편했겠지만 아쉽게도 AutoHotkey는 그렇지 않기 때문에 소문자, 대문자, 첫 글자만 대문자, 파스칼 표기법을 따로 넣어주었습니다. 반복문 안쪽에선 총 네 번 테이블에 대입해준 셈입니다. 그래서 ByRef
라는 키워드는 byref
, BYREF
, Byref
, ByRef
총 네 종류로 테이블에 담기게 됩니다.
사실 이보다 편한 방법이 있었으니...
이 프로젝트에 기여한 것은 한참 전이었습니다. Lite XL이 그 사이에 플러그인 버전을 업데이트 했기 때문에, 이에 대응하기 위해 구조를 바꾸던게 오늘이었습니다. 오늘 코드를 보니 그냥 키워드를 일일이 적지 않고 패턴에 의해 하이라이팅을 했으면 어땠을까 싶었는데, 생각해보니 그 당시에도 그런 생각을 했던 기억이 납니다. 만약 패턴으로 처리했다면 아래와 같은 규칙을 적용했을 것입니다.
A_로 시작하는 문자는 띄어쓰기가 나오거나 괄호가 나올 때까지 내장 변수이다
콤마가 있는 줄에서 첫 번째 콤마의 이전 부분은 명령어이다.
문장의 시작에서, 첫 번째 여는 소괄호 이전 부분은 함수이다.
그러나, 이런 규칙으로 패턴을 작성하면 아래와 같은 문제가 생깁니다.
함수명이 A_로 시작하면 변수로 취급되어 구문 강조된다.
코드줄 중간에 문자열이 있고, 그 문자열 안에 콤마가 있으면 그 콤마 이전이 명령어로 취급되어 구문 강조된다.
물론 이런 상황에 대한 예외 또한 패턴으로 작성해줄 수 있습니다. 그러나 각 상황에 따른 예외를 직접 패턴에 명시해주면 패턴은 상당히 더러워질 것입니다. Spec에 있는 모든 구문을 적어주는 것이 차라리 더 유지보수가 쉬워보였습니다. 게다가 모든 경우의 예외를 처리할 자신은 없습니다. 이런 경우엔 차라리 Whitelist 식으로 확실히 일치하는 구문만 강조되도록 하는 것이 좋다는 판단이었습니다.
플러그인 적용 결과
AutoHotkey 버전 업에 따른 플러그인 Deprecated
이번에 작업하면서 플러그인 이름을 language_autohotkey.lua
에서 language_autohotkey_v1.lua
로 바꿨습니다. AutoHotkey는 작년 말에 v2 버전으로 버전 업 되었습니다. Python2와 Python3가 구문 호환이 되지 않듯이 AutoHotkey 또한 새 버전과 구문이 다릅니다. 따라서 이 포스트에 있는 v1 플러그인은 현시점에선 Deprecated되었다는 점을 말씀 드립니다.
기여 후기
사실 예전에 기여했던 프로젝트이고, 이번엔 그저 프로젝트의 플러그인 구조가 변경됨에 따라 일부 문구를 수정한 것에 지나지 않습니다. 다만 이는 제 최초의 오픈소스 프로젝트 기여입니다. Lua는 눈에 익지만 사실 잘 알고 있는 것은 아닙니다. 다른 플러그인 파일을 참고하여 작성했던 코드이고, 당시에 이해 못했던 전체 코드의 큰 틀이 지금 보니 뚜렷히 보이는 것은 그동안 코드를 헛 짜지 않았다는 마음이 들게 합니다.
150줄 남짓 짧은 플러그인 파일 하나에도 부끄러운 코드가 들어있음에 더욱 많은 경험을 해봐야겠다고 다짐하게 됩니다. 어디가서 내놓기 부끄러운 코드를 사회에서 팀원에게 보여줄 수 있을리는 없습니다. 세월이 지나면 다른 사람의 코드를 보는 것이 능숙해질 때가 올 것입니다. 그때가 되어도 Lite XL에 기여한 경험은 잊지 못할 것입니다.