VS Code가 "전각(2칸)"으로 취급하는 문자 집합을 내가 쓰는 모노스페이스 폰트에서 직접 뽑아 바꿔 넣는 도구와 설명서입니다. 로마숫자(Ⅰ Ⅱ Ⅲ), 숫자+마침표(⒈ ⒉), 원문자(① ②) 같은 문자가 CJK 폰트에서는 2칸으로 그려지는데 VS Code는 1칸으로 세어서 커서를 위아래로 움직이면 열이 어긋나는 문제(microsoft/vscode#136226)를 해결합니다.
English TL;DR — VS Code decides "is this character two cells wide?" with a hard-coded list of CJK blocks (
isFullWidthCharacterinsrc/vs/base/common/strings.ts). East Asian Ambiguous characters that CJK monospace fonts draw at double width (Roman numerals, ⒈, ①, many symbols) are counted as one column, so vertical cursor movement, column selection, word wrap and the minimap drift from what you see. No extension API can change this. This repo generates the correct table from your font's advance widths (gen_fullwidth_ranges.py) and applies it either to an installed VS Code (patch_vscode_fullwidth.py, one-function patch of the bundled JS) or to a source build (--tswritesfullWidthRanges.ts;patches/strings.ts.diffis the font-independent code change).
VS Code는 문자 폭을 실측하지 않습니다. src/vs/base/common/strings.ts의 isFullWidthCharacter()가
고정된 유니코드 블록 4개(U+2E80–D7AF, U+F900–FAFF, U+FF01–FF5E, U+FFE0–FFE6)만 2칸으로 판정하고,
나머지는 전부 1칸입니다. 이 판정 하나가 아래 모든 곳에 쓰입니다.
| 어디서 | 파일 | 영향 |
|---|---|---|
| 커서 상하 이동의 "보이는 열" 계산 | src/vs/editor/common/core/cursorColumns.ts → cursorMoveOperations.ts vertical() |
위아래 이동 시 열이 어긋남 (이 이슈의 증상) |
| 열 선택(Shift+Alt 드래그) | 위와 동일 | 선택 사각형이 어긋남 |
| 워드랩 줄바꿈 위치 | src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts, browser/view/domLineBreaksComputer.ts |
줄이 너무 일찍/늦게 접힘 |
| 줄 렌더링·마우스 히트 테스트 | src/vs/editor/common/viewLayout/viewLineRenderer.ts, browser/viewParts/viewLines/viewLine.ts |
2칸 강제 렌더링 옵션의 대상 문자 |
| 미니맵 | src/vs/editor/browser/viewParts/minimap/minimap.ts |
미니맵 글자 폭 |
| GPU 렌더러 폴백 판단 | src/vs/editor/browser/gpu/viewGpuContext.ts |
|
| HTML 복사 시 폭 | src/vs/editor/common/languages/textToHtmlTokenizer.ts |
- 확장으로는 못 고칩니다.
vscode.d.ts에 문자 폭이나 커서 열 계산에 관여하는 API가 없고,cursorMove명령도 폭 인자를 받지 않습니다. - 2026-09 main에 들어간 설정
"editor.fullwidthCharacterWidth": "twoCells"(#334022)는 전각 문자를 정확히 2칸에 그리는 옵션이지만, 어떤 문자가 전각인지는 여전히 같은 고정 표를 씁니다. 그래서 표를 바꾸는 것이 출발점입니다.
폰트마다 Ambiguous 문자의 폭이 다르므로 사람이 범위를 고르면 항상 어딘가는 틀립니다.
대신 폰트 파일의 hmtx(수평 advance)를 읽어 advance가 정확히 셀 2개인 문자를 모읍니다.
- 폰트 cmap에 있는 문자: 폰트를 믿는다. advance ≈ 2×cell → 2칸, ≈ cell(또는 0) → 1칸.
- 폰트에 없는 문자(다른 폰트로 폴백 렌더링됨): VS Code 원래 표를 따른다.
- 즉
최종 = 폰트_전각 ∪ (VSCode_원래표 − 폰트_반각).
예를 들어 Monoplex CJK Nerd Font에서는 이렇게 나옵니다. 손으로 골랐다면 놓쳤을 사실들입니다.
- 로마숫자 U+2160–2179: 26자 중 18자만 2칸
- ⒈–⒛(U+2488–249B), ①–⑳(U+2460–2473): 전부 2칸
- 박스 문자(U+2500–257F): 전부 1칸
- Nerd Font 아이콘(PUA U+E000–F8FF): 1칸 (Mono 패치본)
- Bold는 Regular와 그리스 문자 8개만 다름, Italic은 동일 → Regular 기준으로 충분
- Python 3 와 fontTools:
pip install fonttools - 사용하는 폰트 파일(.ttf/.otf). 이름으로 주면 OS 폰트 폴더에서 찾습니다.
python gen_fullwidth_ranges.py "Monoplex CJK Nerd Font"
python gen_fullwidth_ranges.py "C:/path/to/MyMono-Regular.ttf" --compare MyMono-Bold.ttf
같은 폴더에 fullwidth-ranges.json이 생깁니다. 출력에서 이런 것을 확인하세요.
warning: not monospace?가 뜨면 그 폰트는 모노스페이스가 아니라 이 방식이 맞지 않습니다.Box drawing ... 0/128 wide처럼 블록별 요약이 기대와 맞는지.removed vs VS Code에 뜨는 문자는 VS Code가 2칸으로 보던 것을 폰트가 1칸으로 그리는 경우입니다(보통 U+303F 정도).
설치본 안의 resources/app/out/vs/workbench/workbench.desktop.main.js에는 isFullWidthCharacter가
function gS(s){return s>=11904&&s<=55215||...} 같은 한 줄로 들어 있습니다. 이 함수만 JSON 표를 이진 탐색하는 함수로 바꿉니다.
# VS Code 를 완전히 종료한 뒤
python patch_vscode_fullwidth.py # Windows 사용자 설치 자동 탐색
python patch_vscode_fullwidth.py --app "/Applications/Visual Studio Code.app/Contents/Resources/app" # macOS
python patch_vscode_fullwidth.py --app /usr/share/code/resources/app # Linux
python patch_vscode_fullwidth.py --restore # 원복
- 원본은
.orig로 백업하고,product.json의checksums(sha256 → base64, 끝의=제거)를 갱신해 "설치가 손상됨" 경고를 막습니다. - VS Code가 업데이트되면 패치가 사라집니다. 커서가 다시 어긋나면 스크립트를 한 번 더 실행하세요. 이미 패치돼 있으면 원복 후 재적용합니다.
- 실행 중인 창은 다시 로드(
Developer: Reload Window)해야 반영됩니다. 렌더러 번들은 창을 열 때만 읽으므로 실행 중 패치해도 안전합니다. - 함수 축약명(
gS)은 버전마다 바뀔 수 있어 스크립트는 함수 본문 패턴으로 찾습니다. 못 찾으면 VS Code가 표 형식을 바꾼 것이니strings.ts를 다시 보세요.
- How to Contribute 대로 준비합니다. Node는
.nvmrc버전, Windows는 C++ Build Tools(Spectre 라이브러리 포함). - 표를 TypeScript로 생성합니다.
python gen_fullwidth_ranges.py "내 폰트" --ts <vscode>/src/vs/base/common/fullWidthRanges.ts patches/strings.ts.diff를 적용합니다(폰트와 무관한 부분:isFullWidthCharacter를FULL_WIDTH_RANGES이진 탐색으로 교체).완성된 예시는 y-kim/vscodegit apply patches/strings.ts.difffullwidth-from-font브랜치와patches/0001-*.patch에 있습니다(이 쪽 표는 Monoplex CJK Nerd Font 기준이므로 그대로 쓰지 말고 2번에서 재생성하세요).npm install→npm run compile→scripts/code.bat(또는./scripts/code.sh).
Windows에서 걸렸던 것 두 가지:
build/npm/preinstall.ts가 Visual Studio를2022|2019폴더로만 찾아서 VS 2026을 못 봅니다.set vs2022_install=C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools로 알려 주면 통과합니다(존재 검사만 함).- VS Code 터미널에서 빌드 결과를 실행하면 물려받은
ELECTRON_RUN_AS_NODE=1때문에 Electron이 일반 Node로 떠서The requested module 'electron' does not provide an export named 'Menu'가 납니다.launch-code-oss.cmd처럼 그 변수와VSCODE_*를 지우고 실행하세요.
표를 바꾼 뒤 "editor.fullwidthCharacterWidth": "twoCells"를 켜면(1.139+), 표에 든 문자를 정확히 셀 2개에 가운데 정렬해 그립니다.
폰트 advance가 이미 정확히 2배라면 눈에 띄는 차이는 없고, 2배에서 살짝 벗어난 폰트에서 정렬이 맞춰집니다.
fullwidth-test.txt를 열고 ⅠⅡⅢ 줄, ⒈⒉⒊ 줄, ①②③ 줄과 ASCII 줄 사이를 ↑↓로 오가며 커서 열이 시각적으로 유지되는지 보세요.
각 줄의 |가 같은 열(21)에 놓이도록 만들어져 있습니다.
- 폰트 특정 표입니다. 폰트를 바꾸면 다시 생성해야 하고, 그래서 이 형태 그대로는 업스트림(microsoft/vscode)에 넣을 수 없습니다. 업스트림용이라면 "Ambiguous를 2칸으로" 설정이나 사용자 지정 범위 설정 형태여야 하고, 먼저 이슈에서 논의해야 합니다.
- 결합 문자·이모지 시퀀스는 코드포인트 단위로 판정합니다(VS Code 원래 동작과 같음). 이모지는 별도의
isEmojiImprecise가 처리합니다. - 굵게/기울임 스타일에서 폭이 다른 글리프가 몇 개 있을 수 있습니다.
--compare로 확인하세요. - 터미널은 xterm.js가 자체 폭 표를 쓰므로 이 패치와 무관합니다.
| 파일 | 역할 |
|---|---|
gen_fullwidth_ranges.py |
폰트 → fullwidth-ranges.json (+ --ts로 fullWidthRanges.ts) |
patch_vscode_fullwidth.py |
설치본 번들 패치 / 원복 / 체크섬 갱신 |
fullwidth-ranges.json |
Monoplex CJK Nerd Font 기준 생성 결과(예시) |
patches/strings.ts.diff |
소스 변경 중 폰트와 무관한 부분 |
patches/0001-*.patch |
소스 변경 전체(예시 표 포함) |
fullwidth-test.txt |
커서 확인용 텍스트 |
launch-code-oss.cmd |
Windows에서 소스 빌드 실행기(환경변수 정리) |
- 이슈: #136226 Vertical cursor movement considering character width
- 관련: #155588 → PR #334022
editor.fullwidthCharacterWidth - 적용 예시 브랜치: https://github.com/y-kim/vscode/tree/fullwidth-from-font
MIT. patches/ 안의 diff는 microsoft/vscode(MIT) 소스에 대한 변경입니다.