This is my personal VScode extension I'm constantly developing in my spare time. It does not focus on specific functionality but rather acts as big collections of shortcuts, macros, tips and tricks I've accumulated over time. Some of them are simple and polished, others are very hacky and at experimental stage.
The functionality of this extension is very tweaked to my taste and hence not suitable for general use. It also uses some scripts from my personal dotfiles repo (private at the moment). However many ideas deserve to be extracted into full-featured plugins and I will be extremely happy to see my ideas borrowed, polished, improved and expanded.
Coming from strong VIM background and being a customization freak I once understood that maintaining a large VIM configuration (to turn VIM into IDE) became too unbearable and unpleasant, for many reasons.
So I had to make a choice between Emacs (obvious candidate) and a "modern" extensible editor such as VScode or Atom. Despite Emacs being known as a "king" of customization I decided to give VScode a try and haven't looked back since.
Just as Emacs power user maintains his large ~/.emacs.d/
config I package all
my custom functionality in this big monolithic extension. This repo is a live
example to demonstrate that VScode is perfectly valid choice for software
developers who want to invest heavily into their most used tool - the EDITOR.
Emacs adepts usually describe writing Emacs lisp as pure joy and fun compared to mainstream procedural/OOP languages but still I think Typescript/Node infrastructure has some pretty big practical advantages:
- Typescript with first-class LSP IDE-like language support in VScode itself. While good language support may be not needed for simple editing macros its crucial for big feature development. I would argue that it would be much easier to develop and maintain something like Magit in Typescript than ELisp.
- Just the debugging support is something I was dreaming about when trying to find errors in my 10K LOC VIM config :). I know the situation is much better in Emacs but IMO nothing can beat proper visual debugger with convenient conditional breakpoints, value preview hovers etc.
- Rich collection of Node.JS packages. Today everything has a Node package or binding. Try integrating Tree-sitter with ELisp!
- Separate installation required for extension to make it work. While some may find this additional step as downside as in VIM or Emacs one just needs to edit text files and reload editor I see it as big plus: if you make some logic bug if will not be applied immediately to your current (after reload) and future editor sessions. So you can use stable version of extension while developing next version in the same Git repository. Then instantly you can test new code in a sandboxed editor window.
At the time I started using VScode its tasks support was much more limited than it is now so I decided to write my own task definition format which use native tasks under the hood. Task definitions schema (see params.ts ) expands that native tasks and adds some small and useful features:
- Global, per-workspace and per-folder task definition files.
- Dictionary
{ 'task-name': <task definition>, ....}
instead of array where the most simple form of task definition may be just a string of shell command. - More customizable terminal reveal options, on success and on failure hooks and conditions.
- Advanced template substitution.
- Task output can be parsed to list of code locations and presented in peek view.
- Search tasks - using template substitution search for some text and present
results in peek view. Example: using current word as C struct field and
search for
.word
or->word
to find all references of this field. - Conditional tasks - tasks available only if some condition holds. Example: git-related tasks
- are showed only if
.git
exists in root directory. - Multi-folder tasks - for workspace with multiple workspace folders a task can
be defined to run in all folders at once. Example: run
git pull
in all workspace folders that are git repos.
Tree-sitter is modern syntax parser that supports many languages. It's very fast and robust and has Javascript bindings. Originally developed for Atom editor it is now being used in other editors and IDEs. I'm utilizing it for specific purpose - tree-like code navigation. Think of Par Edit for non-Lisp languages!
Treating source code as syntax tree and navigating this tree with cursor keys in a modal fashion can be much faster than traditional by-character/line/word navigation. Also by moving tree nodes around one can do some easy refactoring such as swapping function arguments, "unwrapping" if blocks etc.
This feature is at experimental phase and more detailed description to follow.
Similarly to VIM's remote feature there is a server API and CLI tool that allows to list open VScode windows and to execute specific command in given window. Very handy for integrating VScode with external tools.
Navigating backward/forward in location history is one of the more important features of code editor. However, VScode's builtin history implementation is lacking in many areas.
- Up until recent
Februrary 2022 release
the history scope was entire window i.e. all cursor changes in entire window
constituted single history stack. If like me you're accustomed to dual-pane
editing where two editing panes are independent then this scope model is a big
obstacle since going back in history would unexpectedly jump between panes.
Fortunately new
workbench.editor.navigationScope
setting can be used to limit the scope to editor group. - TODO: too verbose
- TODO: no way to visualize.
- Switching to alternate (e.g. C source <=> header file).
- Automatically copy (
scp
) saved files to remote destination. - Generic Call Hierarchy provider for any language and supports "Go to definition" and "Go to symbol" LSP features.
- Fix color theme for current workspace - useful when sharing same workspace/folder config file between multiple work trees and having different color themes for each tree.
- Ctags support for document outline and local symbol jumps.
- Extract string from diagnostics (e.g. ESLint rule names) and add them to auto-completion.
- Document edit history - select previously edited/inserted text.
- Relative line navigation - similarly to VIM's
relativenumber.
jump a number of lines up/down with
Ctrl + <number>
andAlt + <number>
. This involves auto-generatingkeybindings
section inpackage.json
. - GNU Global (gtags) support including definition and workspace symbols providers. There is also a custom QuickPick based implementation of Go to symbol in workspace... command that is MUCH faster than native one and is actually usable.
- VIM-like jumplist. It's not secret and forward/backward navigation in VScode is not ideal as it "remembers" even unimportant jumps such as page up/down or find results navigation. This is an attempt to remember only import jumps (e.g. definition/reference jumps) for faster navigation.
- Conveniently deselect of specific selection in multiple cursors.
- Quickly rerun previous definition/references search.
- Mark selected text with
Ctrl+C
, swap marked text with selection. - Quickly navigate to adjacent functions using peek view.
- Edit remote files over SSH (kind of Emacs's Tramp) by using virtual filesystem provider.
Some advanced feature to help managing alone such a big repository:
The extension functionality is divided into modules each module implemented in
separate file or subdirectory. Each modules has its own activate()
function
and registers itself. When some modules depends (imports) another module they
will be activated in a proper order.
TODO: Write this section.
TODO: Update with esbuild
.
Webpack is a MUST for such a big codebase. I use a pretty standard webpack config with some quirks needed for support of native-compiled Node modules. The code is auto-formatted with Prettier (config). I also use rather verbose ESLint config for linting.
I use pretty verbose logging to help me quickly diagnose the problems without using debugger too much. I had to create custom logging framework to answer my needs. Beyond common features usually found in logging frameworks it has some nice and/or VScode-specific stuff I haven't seen elsewhere:
- Log is dumped to VScode output channel AND to disk file at different configurable log levels.
- VScode output channel has a dedicated syntax highlighting theme which is only used for this channel.
- Optional python-like formatting using string-format package.
- Log callsite in Typescript code using source-map-support and callsites packages.
While errors during commands are shown to use in popup notification boxes, there are many cases where exception raised in code or rejected promise are just printed to debug console without notifying user:
- VScode event listeners.
- Language features providers.
- VScode and Node API callbacks.
I care to wrap all commands, event handlers and API callbacks with custom error handlers which show popup notification. Some errors are not critical (like command precondition) and are shown in a less intrusive way in status bar.
The dialog box asks to request accessibility for
Code - OSS Helper (Renderer).app
. The app is not in /Applications
but in
/Applications/Code - OSS.app/Contents/Frameworks/Code - OSS Helper (Renderer).app
.
One way to select it is to use "Go to location" by pressing Cmd+Shift+G
.
The extension uses some binary packages (e.g. tree-sitter
). In order to keep
up with pace at which VScode updates to newer Electron version I need to
maintain my own forks of them and sometimes do tricks in order to install them.
Some packages can't be installed directly from git because they require some packaging step. The flow for upgrading this packages:
- Checkout the package and build it locally, with proper version of Electron.
- Run
npm pack
to generate.tgz
archive. - Copy archive to
local
dir in extension's root dir. - Remove entry in lock file.
- More features!
- Code structure reorganization.
- Comments, docstrings.
Enjoy!