Conceal makes visual substitutions to your source code, e.g. displaying fun
as λ
, while never touching your code.
This feature is inspired by prettify-symbols-mode for Emacs and is the unofficial successor of vsc-prettify-symbols-mode.
This extension is currently developed and maintained as part of Romain Tetley, and contributors.
Once you have installed this extension, modify settings.json
to add language-specific substitutions. For example, the following settings will target F# files, rendering fun
as λ
, ->
as ⟶
, and place a border around parameters.
"conceal.substitutions": [{
"language": "fsharp",
"substitutions": [
{ "ugly": "fun", "pretty": "λ", "scope": "keyword.other.function-definition.fsharp" },
{ "ugly": "->", "pre": "[^->]", "post": "[^->]", "pretty": "⟶" },
{ "ugly": ".+", "scope": "variable.parameter.fsharp", "pre": "^", "post": "$", "style": { "border": "1pt solid green" } }
]
}]
A substitution matches any string that satisfies the "ugly"
pattern, visually replacing it with "pretty"
and/or applying style via "style"
. You can optionally specify the context by providing "pre"
or "post"
regular expressions that must be matched for the substitution to occur. Or you can specify a syntactic scope in which to perform the substitution. You can also target multiple languages or glob patterns at once via "languages": ["fsharp", {"pattern": "**/*.txt"}]
.
Note: scope support is experimental and only available on versions of vscode older than 1.21.1.
By default, regular expressions match against a whole line of text. If "scope"
is specified, then regular expression matches will only be performed on the parsed [TextMate] tokens that match the given scope. A small subset of TextMate scope expressions are supported. For example, a substitution with scope "source.js comment"
will match a token with scope "text.html.basic source.js comment.block.html"
. A scoped "ugly"
regular expression must match the entire token by default -- i.e. "pre"
and "post"
are respectively set to "^"
and "$"
by default when a scope is specified. However, "pre"
and "post"
can be overriden to allow multiple substitutions within a single token (e.g. a comment).
Tip: use scope-info to see the scope assigned to each token in your source.
By default, "ugly" text will be revealed while contacted by a cursor. You may override this behavior by specifying "conceal.revealOn"
, or per-language by specifying "revealOn"
within a language entry. Options are:
"cursor"
: reveal while a cursor contacts the symbol (default);"cursor-inside"
: reveal while a cursor is inside the symbol;"active-line"
: reveal all symbols while on the same line as a cursor;"selection"
: reveal all symbols while being selected or in contact with a cursor; or"none"
: do not reveal symbols.By default, any "pretty" symbol that comes into contact with the cursor will be rendered with a box outline around it. This effect is only visible if the "ugly" text is not revealed (e.g. "revealOn": "none"
). You can control this setting by specifying "conceal.prettyCursor"
, or per-language by specifying "prettyCursor"
within a language entry. Options are:
"boxed"
: display a box around a symbol (only visible if the "ugly" text is not revealed); or"none"
: do not change the appearance of the symbol.By default, cursor movement will traverse the characters of the "ugly" text -- this will cause it to become invisible while inside the text if it is not revealed (see "revealOn"
). Setting "conceal.adjustCursorMovement"
to true
will tweak cursor movement so that "pretty" symbols behave as a single character. This can be overriden per-language by specifying "adjustCursorMovement"
in a language entry. In particular, left or right movement will cause the cursor to jump over the symbol instead of going inside. However, this setting does not currently account for all kinds of cursor movement, e.g. up/down.
A tiny subset of CSS can be used to apply styling to the substitution text by setting "style"
; styles can be specialized for light and dark themes. If "pretty"
is not specified, then"style"
must be specified: the result being that all "ugly" matches will have the style applied to them instead of being substituted.
"border", "backgroundColor", "color", "textDecoration"
(this list is limited by vscode)."dark": {"color": "white"}, "light": {"color": "black"}
"hackCSS": "font-style: italic, font-size: 2em"
(this can easily break rendering)This extension uses Javascript's regular expression syntax for "ugly"
, "pre"
, and "post"
(but double-escaped because it is parsed by both JSON and regexp). You must avoid using capturing-groups or mis-parenthesized expressions as it will cause substitutions to behave unpredictably (validation is not performed so you will not receive an error message).
The following commands are available for keybinding:
conceal.copyWithSubstitutions
: copy selected text with "pretty" substitutions appliedconceal.enablePrettySymbols
: globally enable prettify symbols modeconceal.disablePrettySymbols
: globally disable prettify symbols modeconceal.togglePrettySymbols
: globally toggle prettify symbols modesettings.json
"conceal.renderOn": "cursor",
"conceal.adjustCursorMovement": false,
"conceal.renderOn": "none",
"conceal.adjustCursorMovement": true,
Check out Monospacifier to fix your fonts!
Quick start example: if your editor font is Consolas, download and install the XITS Math fallback font for Consolas, then add the following to settings.json
:
"editor.fontFamily": "Consolas, 'XITS Math monospacified for Consolas', 'Courier New', monospace"
Tip: submit new issues on github
"conceal.adjustCursorMovement"
is set to true
See the wiki for more examples ‐ and contribute your own!
The following shows a brief subset of useful substitutions for Haskell, OCaml, and F#:
"conceal.revealOn": "cursor",
"conceal.adjustCursorMovement": false,
"conceal.substitutions": [{
"language": "haskell",
"revealOn": "active-line",
"substitutions": [
{ "ugly": "\\\\", "pretty": "λ", "post": "\\s*(?:\\w|_).*?\\s*->" },
{ "ugly": "->", "pretty": "→" },
{ "ugly": "==", "pretty": "≡" },
{ "ugly": "not\\s?", "pretty": "¬", "pre": "\\b", "post": "\\b" },
{ "ugly": ">", "pretty": ">", "pre": "[^=\\-<>]|^", "post": "[^=\\-<>]|$" },
{ "ugly": "<", "pretty": "<", "pre": "[^=\\-<>]|^", "post": "[^=\\-<>]|$" },
{ "ugly": ">=", "pretty": "≥", "pre": "[^=\\-<>]|^", "post": "[^=\\-<>]|$" },
{ "ugly": "<=", "pretty": "≤", "pre": "[^=\\-<>]|^", "post": "[^=\\-<>]|$" }
]},{
"language": ["ocaml", {"pattern": "**/*.{ml}"}],
"revealOn": "none",
"adjustCursorMovement": true,
"substitutions": [
{ "ugly": "fun", "pretty": "λ", "pre": "\\b", "post": "\\b" },
{ "ugly": "->", "pretty": "→", "pre": "[^->]", "post": "[^->]" },
{ "ugly": "List[.]for_all", "pretty": "∀", "pre": "\\b", "post": "\\b" },
{ "ugly": "List[.]exists", "pretty": "∃", "pre": "\\b", "post": "\\b" },
{ "ugly": "List[.]mem", "pretty": "∈", "pre": "\\b", "post": "\\b" },
{ "ugly": "\\|", "pretty": "║", "pre": "^\\s+" }
]},{
"language": "fsharp",
"substitutions": [
{ "ugly": "fun", "pretty": "λ", "pre": "\\b", "post": "\\b" },
{ "ugly": "->", "pretty": "→", "pre": "[^->]", "post": "[^->]" },
{ "ugly": "List[.]forall", "pretty": "∀", "pre": "\\b", "post": "\\b" },
{ "ugly": "List[.]exists", "pretty": "∃", "pre": "\\b", "post": "\\b" },
{ "ugly": ">>", "pretty": "≫", "pre": "[^=<>]|^", "post": "[^=<>]|$" },
{ "ugly": "<<", "pretty": "≪", "pre": "[^=<>]|^", "post": "[^=<>]|$" },
{ "ugly": "\\|", "pretty": "║", "pre": "^\\s+" }
]}]