VSCode/TextMate syntax highlighting grammar -- matching code until end of line - visual-studio-code

I am writing a TextMate grammar to implement syntax highlighting in VSCode for a custom flavor of Markdown. I would like everything on the same line after ##$ to be highlighted as Javascript.
This is what I came up with:
"majsdown_execute_statement": {
"begin": "(.*?)(##\\$)",
"name": "test",
"end": "(\\r\\n|\\r|\\n)",
"beginCaptures": {
"2": {
"name": "keyword.control.majsdown"
}
},
"patterns": [
{
"include": "source.js"
}
]
},
That almost works:
But I would like the ##$ part to always be highlighted as a keyword. Here's a mockup (edited image) of my desired result:
I've tried a lot of different combinations of "begin" and "end", and I've also tried many nested patterns like the following one:
"patterns": [
{
"begin": "\\s",
"while": "^(\\r\\n|\\r|\\n)",
"patterns": [
{
"include": "source.js"
}
]
}
]
Unfortunately, nothing provides the result I desire. How can I achieve my desired result?

To handle multiline constructs, I think you'll have to provide modified embedded language(JavaScript) definition as well. Easiest way to do it would be to make ##$ a comment in JavaScript, so it'll not mess up existing constructs.
I have no idea about VScode syntax highlighting. I'll try to demonstrate the idea using HighlightJs. It has very similar way of defining a language.
Demo: View in full page mode.
hljs.debugMode();
// default code for the demo
src.innerText = `
My custom markdown highlighted with custom rules.
Here is how javascript code looks:
##$ function test()
##$ {
##$ // TODO: stuff
##$ let rand = Math
##$ .random()
##$ .toFixed(2);
##$ }
##$ var string = "hello";
##$ const string2 = \`some long
##$ string\`;
Leave one empty line to get out of the code block.
Here is some more code:
##$ var rand = Math.random();
##$ console.log(rand);
We are out of the second code block now.
`;
// define our markup language, say 'mdown'
let langDef = {
name: 'Mdown',
aliases: ['mdown'],
case_insensitive: true,
contains: [
{
className: 'mscript',
begin: /##/,
end: /\$/,
keywords: { name: 'mscript' },
contains: [],
starts: {
end: /^\s*$/, // end on empty line
returnEnd: true,
subLanguage: ['js'], //embedded language
},
},
],
};
hljs.registerLanguage('mscript', () => langDef);
// patch javascript multiline structures
let js = hljs.getLanguage('javascript');
for (let c of js.contains) {
if (c.begin === "`") { // handle templet literals
c.contains.unshift({
begin: /^##/,
"relevance": 0,
end: /\$/,
contains: [],
scope: 'mscripttag'
})
}
}
// console.log(js);
// make '##$' a comment :)
// So it'll not mess existing styling
js.contains.push(hljs.COMMENT(/##/, /\$/, { scope: 'mscripttag', relevance: 10 }));
// for demo update highlighted code on user input
let handleChange = (event) => {
let html = hljs.highlight(src.innerText, { language: 'mscript', ignoreIllegals: true }).value;
code.innerHTML = html;
};
// javascript patching done
document.addEventListener('DOMContentLoaded', handleChange);
src.addEventListener('input', handleChange);
body { font-size: .8rem; }
.input {
width: 46%;
background-color: #eee;
display: inline-block;
overflow: auto;
}
.output {
width: 50%;
display: block;
float: right;
background-color: #ccc;
}
/* add extra theme for our tag ##$ */
.hljs { color: #bbb !important; }
.hljs-mscript,
.hljs-mscripttag { color: red; }
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/base16/snazzy.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/highlight.min.js"></script>
<div>Edit below markdown code.</div>
<pre class="input"><div id="src" contenteditable></div></pre>
<pre class="output"><code id="code" class="javascript hljs">abcd</code></pre>
In case the library not available, here is how the output looks like:
In above code I have defined ##$ <jscontent> \n as a tag in our markdown language definition. And the content will be processed according to the embedded language JavaScript. Next I've modified the default JS language definition and added ##$ as a comment so it'll be harmless to exiting syntax. Now we just need to handle template literals which are multiline. In the literal definition I've added ##$ as a part of the literal, but with different scope/name/styleOption msscripttag.
I hope you'll be able to define similar syntax in TextMate for VSCode.

try using match instead...
"patterns": [
{
"name": "keyword.control.factory",
"match": "(\\w|-)[^(]*"
}
]
this code matches everything up to a "(" character
so your code look something like this:
"majsdown_execute_statement": {
"patterns": [
{
"name": "keyword.control.majsdown",
"match": "(?<=##\$)\\w*"
}
]
}
you can also try [^##$]*(\\w)
try it out here

Related

VSCode settings : colorize custom tags that start with the "x-" prefix

I want to write a vscode setting in which tags ( inside .vue file ) that start with the prefix x- will be colored green.
tags like <x-component></x-component> and <x-lorem></x-lorem>
is it possible? and how
i tried this
"editor.tokenColorCustomizations": {
"textMateRules": [
{
"scope": "entity.name.tag.x-*",
"settings": {
"foreground": "#00ff00"
}
}
]
},
and it not works
Unfortunately, it seems that this is not possible without installing the plugin
I used the Highlight plugin mentioned by #rioV8
it works
this is my config :
"highlight.regexes": {
"(<[^>]*x-[^>]*>)": { // A regex will be created from this string, don't forget to double escape it
"decorations": [ // Decoration options to apply to the capturing groups
{ "color": "#7ac379" }, // Decoration options to apply to the first capturing group, in this case "//<-x:"
]
}
}

Is it possible to EDIT built-in Emmet abbreviations in VSC?

I'd like to be able to edit default behaviour of "!" instead of creating my own version of "!" from scratch.
Is it possible to edit (tweak) default behaviours of Emmet abbreviations in VSC?
Yes! From the docs:
Custom Emmet snippets need to be defined in a json file named snippets.json. The emmet.extensionsPath setting should have the path to the directory containing this file.
{
"html": {
"snippets": {
"ull": "ul>li[id=${1} class=${2}]*2{ Will work with html, pug, haml and slim }",
"oll": "<ol><li id=${1} class=${2}> Will only work in html </ol>",
"ran": "{ Wrap plain text in curly braces }"
}
},
"css": {
"snippets": {
"cb": "color: black",
"bsd": "border: 1px solid ${1:red}",
"ls": "list-style: ${1}"
}
}
}
More info here.
You can also modify existing blocks using filters.

How to remove styles from a textmate scope in VSCode

How can I remove the styles set on a textmate scope by a theme? The theme I am using sets a font weight on the storage.type.function scope, and I want to remove it so that it inherits from a parent scope.
Both keywords have the most specific scope as storage.type.function, which is set to bold by the theme.
I have used the two less-specific scopes meta.function and meta.function.closure to set regular and bold styles, however both these are overridden by the more specific storage.type.function scope set by the theme.
Thus to get my custom style to apply, I need to somehow remove the style set on storage.type.function so it falls back to the meta values I have set. Setting it to blank doesn't work:
{
"workbench.colorTheme": "Solarized Dark",
"editor.tokenColorCustomizations": {
"textMateRules": [
// Want to unset this style
{ "scope": "storage.type.function", "settings": { "fontStyle": "" } },
// Neither of these apply because the one above overrides them
{ "scope": "meta.function", "settings": { "fontStyle": "" } },
{ "scope": "meta.function.closure", "settings": { "fontStyle": "bold" } },
]
}
How can I unset storage.type.function so that the meta styles apply?
Here is some test PHP to reproduce the issue, save it as example.php and open it in VSCode:
<?php
class Test {
function a() { // "function" should not be bold (meta.function)
$b = function () {}; // "function" should be bold (meta.function.closure)
}
}

Chrome apps webview tag - how to inject CSS or JS in time?

I'm writing an application which should embed specific website into a <webview> and inject some CSS and JS code to adapt this website for viewing on certain touch-sensitive device.
The problem is that I can't find a way to inject my code when page is loaded, instead the code is injected AFTER the page is rendered and, as result, all modifications become visible.
While code injection perfectly works with chrome extensions and content script (by setting run_at attribute to document_end on manifest.json, this is not the case for webviews.
This is my code:
manifest.json
{
"name": "App",
"version": "0.0.1",
"manifest_version": 2,
"app": {
"background": {
"scripts": [ "main.js" ]
}
},
"permissions": [
"webview"
]
}
main.js
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('index.html', { state: "normal" },
function(win) {
win.contentWindow.onload = function() {
var wv = this.document.querySelector('webview');
wv.addEventListener('contentload', function(e) {
this.insertCSS({ code: "body { background: red !important; }" });
});
}
}
);
});
index.html
<webview src="https://developer.chrome.com/apps/tags/webview" style="width: 100%; height: 100%;"></webview>
The same on the Gist: https://gist.github.com/OnkelTem/ae6877d2d7b2bdfea5ae
If you try this app, you will see that only after the webview is loaded and fully rendered my CSS rule is applied and the page background becomes red. In this example I use contentload webview event, but I also tried all other webview events: loadstart, loadstop, loadcommit - with no any difference.
I tried also using webview.contentWindow, but this is object is EMPTY all the time, despite documentation states it should be used.
Any ideas? Is it possible at all?
First of all, use the loadcommit event instead of the contentload event.
Second, add runAt: 'document_start' to the webview.insertCSS call (this also applies to webview.executeScript, if you ever want to use it). The implementation of executeScript is shared with the extension's executeScript implementation, but unfortunately the app documentation is incomplete. Take a look at chrome.tabs.insertCSS until the app documentation is fixed.
Here is an example that works as desired:
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('index.html', { state: 'normal' },
function(win) {
win.contentWindow.onload = function() {
var wv = this.document.querySelector('webview');
// loadcommit instead of contentload:
wv.addEventListener('loadcommit', function(e) {
this.insertCSS({
code: 'body { background: red !important; }',
runAt: 'document_start' // and added this
});
});
}
}
);
});
Note: Although the previous works, I recommend to put the script that manipulates the webview in index.html, because the resulting code is much neater.
// main.js
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('index.html', { state: 'normal' });
});
<!-- index.html -->
<webview src="https://developer.chrome.com/apps/tags/webview" style="width: 100%; height: 100%;"></webview>
<script src="index.js"></script>
// index.js
var wv = document.querySelector('webview');
wv.addEventListener('loadcommit', function() {
wv.insertCSS({
code: 'body { background: red !important; }',
runAt: 'document_start'
});
});

Conditional statements with SCSS

Does anyone know if this is possible:
I want my brand colour to be $brand: #00cccc;
however, I want to change that on just one page, and that page is defined by a class on it's body.
body class="purple"
Now my mind is thinking that this would be ideal:
body.$class {
#if $class == 'purple'
{
$brand = #ff0033;
}
#else
{
$brand = #00cccc;
}
}
But that's not correct syntax at all.
Is this something that can be done in a similar way?
I believe you are looking for something like this?
#each $class in purple, none {
$brand: #00cccc;
$selector: 'body';
#if $class == purple {
$brand: #ff0033;
$selector: 'body.' + $class;
}
#{$selector}{
/* Use $brand */
}
}
I don't believe there is an #if statement you could create for that. In LESS you could redeclare the same variable inside body.purple and it would only apply inside that block, but in Sass/Scss redeclaring the variable will change it globally. This might not be ideal but you could redeclare the variable twice, once at the top for the purple color, and once at the bottom to set it back to its default value.
scss
// Global Variables
$blue: #0cc;
$purple: #f03;
$brand: $blue;
body {
color: $brand; // blue
}
.purple {
$brand: $purple;
color: $brand; // now purple
// all other styles
$brand: $blue;
}
.test {
color: $brand; // back to blue
}
css output
body {
color: #00cccc;
}
.purple {
color: #ff0033;
}
.test {
color: #00cccc;
}
Here is an article about the variable scope differences between LESS and Sass. http://blog.freeside.co/post/41774744757/less-vs-sass-variable-scopes
Yes, as of 2022 Sass does include #if and #else rules for flow control. More info on the Sass documentation site.
The #if rule is written #if { ... }, and it controls whether or not its block gets evaluated (including emitting any styles as CSS). The expression usually returns either true or false—if the expression returns true, the block is evaluated, and if the expression returns false it’s not.
For example, this Sass code:
$light-background: #f2ece4;
$light-text: #036;
$dark-background: #6b717f;
$dark-text: #d2e1dd;
#mixin theme-colors($light-theme: true) {
#if $light-theme {
background-color: $light-background;
color: $light-text;
} #else {
background-color: $dark-background;
color: $dark-text;
}
}
.banner {
#include theme-colors($light-theme: true);
body.dark & {
#include theme-colors($light-theme: false);
}
}
...will produce this CSS:
.banner {
background-color: #f2ece4;
color: #036;
}
body.dark .banner {
background-color: #6b717f;
color: #d2e1dd;
}
There is also support for #else if and Boolean operators and, or, and not.