VS Code에서 IntelliJ의 ⌘B = Go to Declaration or Usages 직접 구현해 보기
IntelliJ를 오래 쓰다가 VS Code로 넘어오면서 가장 먼저 어색해지는 것 중 하나가 네비게이션이었다. 특히 ⌘B.
IntelliJ에서는 ⌘B 하나로 꽤 자연스럽게 움직인다.
- 호출부에서는 선언/정의로 이동하고,
- 이미 선언 위치에 있으면 usages를 보여준다
반면 VS Code는 기본적으로 이 기능이 분리되어 있다.
- Go to Definition
- Go to Declaration
- Find References
- Peek References
settings 와 keybindings 조합만으로는 IntelliJ의 Go to Declaration or Usages 를 완전히 복제하기 어렵다.
그래서 이번에는 아예 VS Code Extension 을 하나 만들어서 ⌘B 전용 동작을 구현해 봤다.
문제 정의
내가 원하는 동작은 딱 이거였다.
- 현재 심볼에서 declaration 을 먼저 찾는다.
- 외부 declaration target 이 있으면 거기로 이동한다.
- declaration 이 현재 위치뿐이라면 usages를 peek 로 보여준다.
- declaration 이 없으면 definition 을 찾는다.
- definition 도 현재 위치뿐이면 usages를 peek 로 보여준다.
즉, IntelliJ의 Go to Declaration or Usages 를 VS Code식 provider API 위에서 다시 조합하는 방식이다.
왜 settings.json 으로는 부족했나
VS Code에는 editor.gotoLocation.alternativeDefinitionCommand 같은 설정이 있다. 언뜻 보면 “현재 위치면 references로 넘겨라” 같은 fallback 을 만들 수 있을 것 같다.
하지만 실제로는 한계가 있다.
- built-in go-to 명령에만 적용된다.
- provider 가 현재 위치와 정확히 같다고 판단할 때만 fallback 이 자연스럽게 걸린다.
- declaration / definition / references 가 언어 서버 단에서 분리되어 있어 IntelliJ와 완전히 같은 semantics 는 보장되지 않는다.
그래서 결국 ⌘B 자체를 커스텀 명령으로 만드는 쪽이 맞았다.
구현한 확장 구조
확장 이름은 custom-intellij-nav 로 잡았다.
핵심 command id 는 다음과 같다.
"intellij.goToDeclarationOrUsages"
이 커맨드는 VS Code의 built-in command 들을 조합해서 동작한다.
- vscode.executeDeclarationProvider
- vscode.executeDefinitionProvider
- vscode.executeReferenceProvider
- editor.action.goToLocations
- editor.action.peekLocations
extension.ts 핵심 포인트
구현에서 중요했던 포인트는 두 가지였다.
1. declaration 이 현재 위치면 definition 으로 넘어가지 않는다
처음에는 declaration 결과가 현재 위치뿐일 때 definition provider 를 한 번 더 호출하는 구조를 생각했다.
그런데 이건 IntelliJ semantics 와 다르다.
내가 원하는 건 “현재 declaration 위치에 있으면 usages” 이지, “한 번 더 definition 을 찾아본다”가 아니기 때문이다.
그래서 로직을 이렇게 바꿨다.
- declaration 결과가 있으면 그걸 우선 신뢰
- 외부 target 이 있으면 이동
- 외부 target 이 없으면 usages peek
- declaration 자체가 없을 때만 definition 시도
2. stale request 방지
대형 TypeScript 레포에서는 provider 응답이 늦을 수 있다. 사용자가 심볼 위에서 ⌘B 를 누르고 금방 다른 파일이나 다른 위치로 이동할 수도 있다.
이때 늦게 도착한 결과가 현재 에디터를 잘못 점프시키면 UX 가 꽤 나빠진다.
그래서 request id 와 editor snapshot 을 같이 저장했다.
- 현재 document uri
- document version
- current position
- latest request id
비동기 응답이 돌아왔을 때
- 더 최신 요청이 있거나
- active editor/document/version 이 달라졌으면
그 결과를 무시하도록 했다.
개발 중 만난 실제 문제들
1. Extension Development Host 가 빈 창만 뜸
처음 F5 를 누르면 새 Extension Development Host 창이 뜨긴 하는데, 테스트용 파일이 없어서 뭘 확인해야 할지 애매했다.
해결은 간단했다.
확장 프로젝트 루트 아래에 sample-workspace 를 만들고, launch.json 의 args 에 그 경로를 넣었다.
"args": [
"${workspaceFolder}/sample-workspace",
"--extensionDevelopmentPath=${workspaceFolder}"
]
2. sample-workspace 추가 후 compile 에러
tsconfig.json 의 rootDir 이 src 인데, 기본 include 패턴 때문에 sample-workspace/main.ts 가 컴파일 대상에 들어오면서 TS6059 가 발생했다.
해결은 include 를 명시하는 것이었다.
"include": [
"src/**/*"
]
이렇게 하니 extension source만 compile 대상이 되었다.
3. vsce package 가 Node 18 에서 깨짐
로컬 Node 가 18이었는데, @vscode/vsce 는 Node 20+ 를 요구했다. undici 쪽에서 File is not defined 로 바로 터졌다.
이건 extension 코드 문제가 아니라 packaging toolchain 런타임 문제였다.
그래서 Node 22 로 올리는 게 맞다.
4. README 템플릿 문구 때문에 패키징 거부
vsce package 를 돌렸더니 이런 에러가 나왔다.
> It seems the README.md still contains template text.
generator-code 가 만들어 준 기본 README 문구를 그대로 두면 vsce 가 패키징을 막는다. Marketplace 에 템플릿 상태로 올라가는 걸 방지하려는 장치인 것 같다.
현재 어디까지 됐나
좋았던 점은 여기까지는 이미 검증됐다는 것이다.
- 명령이 Command Palette 에 노출됨
- sample workspace 에서 호출부 → 선언 이동 성공
- 선언 위치에서 재실행 시 references peek 성공
즉, extension semantics 는 1차 성공이다.
아직 남은 작업
남은 작업은 이렇다.
- Node 22 환경으로 전환
- README / CHANGELOG / LICENSE 정리
- VSIX 패키징
- 메인 VS Code 프로필에 설치
- 사용자 keybindings.json 에서 cmd+b 소유권 이전
- 실제 linkareer workspace 에서 통합 검증
- GitHub 공개 저장소 정리
- Marketplace publisher / PAT 준비 후 publish
keybindings 전략
이번 작업에서 핵심은 이것이었다.
cmd+b semantics 는 extension 이 소유하고, 키 충돌 해제는 user keybindings 가 소유한다.
실제 사용자 keybindings 는 이렇게 간다.
{
"key": "cmd+b",
"command": "-workbench.action.toggleSidebarVisibility"
},
{
"key": "cmd+b",
"command": "-editor.action.goToDeclaration",
"when": "editorTextFocus"
},
{
"key": "cmd+b",
"command": "intellij.goToDeclarationOrUsages",
"when": "editorTextFocus"
}
이렇게 해야 VS Code 기본 cmd+b 와 IntelliJ keymap extension 이 등록한 cmd+b 를 모두 비활성화하고, 최종 소유권을 내 커스텀 확장 명령으로 넘길 수 있다.
custom-intellij-nav 진행 요약
목표
VS Code에서 IntelliJ의 ⌘+B = Go to Declaration or Usages 동작을 최대한 가깝게 재현하는 커스텀 익스텐션을 만든다.
현재까지 완료
- yo code 로 TypeScript 기반 VS Code Extension scaffold 생성
- package.json 에 커맨드 기여 추가
- intellij.goToDeclarationOrUsages
- main: ./out/extension.js
- src/extension.ts 를 IntelliJ 스타일 네비게이션 로직으로 교체
- declaration 우선
- 없으면 definition
- 현재 위치면 usages peek
- stale request 방지 로직 포함
- tsconfig.json 에 include: ["src/**/*"] 추가
- sample-workspace 가 TypeScript compile 대상에 끼어들어 rootDir 에러를 내던 문제 해결
- sample-workspace/ 추가
- foo.ts
- main.ts
- .vscode/launch.json 수정
- F5 시 빈 Extension Development Host 대신 sample-workspace 가 열리도록 설정
- npm run check 통과
- npm run compile 통과
- Extension Development Host 에서 Command Palette 로 IntelliJ Navigation: Go to Declaration or Usages 명령 노출 확인
- sample-workspace 에서 호출부 → 선언 이동, 선언부 → references peek 동작 확인
현재 확인된 이슈
- npx vsce package 실패
- 원인: Node.js 18 환경에서 @vscode/vsce 가 요구하는 Node 20+ 조건 불충족
- README.md still contains template text 오류
- 원인: generator 기본 README 문구가 남아 있어 vsce package 가 차단됨
아직 안 한 작업
- Node.js 22로 전환
- node_modules, package-lock.json 재설치
- README.md 실사용 문서로 교체
- VSIX 패키징
- 메인 VS Code 프로필에 VSIX 설치
- keybindings.json 에 cmd+b 소유권 이전
- settings.json 정리
- linkareer 실제 workspace 에서 통합 검증
- GitHub README/CHANGELOG/LICENSE 정리
- Marketplace publisher / PAT 생성 및 publish
다음 작업 순서
- Node 22 전환
- README.md 교체
- npx vsce package
- VSIX 설치
- keybindings.json 적용
- settings.json 정리
- 실레포 검증
- GitHub / Marketplace 공개 준비
8. 회고 포인트
- VS Code는 command, provider, keybinding, editor setting 이 분리되어 있어 IntelliJ UX를 그대로 재현하려면 settings 가 아니라 extension 코드가 중심이 되어야 했다.
- cmd+b semantics 는 extension 이 소유하고, 키 충돌 해제는 user keybindings 가 소유하는 구조가 유지보수성이 가장 좋다.
마무리
이번 작업에서 가장 크게 느낀 건, VS Code는 IntelliJ처럼 “한 액션 안에 여러 분기 로직이 들어 있는 UX” 를 기본 제공하지 않는 대신, 그걸 extension 으로 다시 조합할 수 있을 정도로 command / provider API 가 잘 나뉘어 있다는 점이었다.
설정만으로 해결되지 않는 문제를 extension 코드로 풀어냈다는 점에서 꽤 재미있는 작업이었다.
다음 단계는 VSIX 설치와 실제 대형 TypeScript workspace 에서의 통합 검증이다.
그 단계까지 마치면, 그때는 진짜로 “VS Code에서 IntelliJ 스타일 ⌘B 를 쓸 수 있다”고 말해도 될 것 같다.