Steps to Reproduce:
Nunjucks plugin.p and press tab.\t was inserted.Expected Result:
p expanded to <p></p>.I was looking for the setting to enable emmet, but it seems hard-coded in the source (src/vs/workbench/parts/emmet/node/editorAccessor.ts#L20).
Can we modify this value through preference?
Thanks.
I think thats an issue that comes up not the first time (#4962 or #4700 for example) but was always phrased as "support emmet in filetype x". I think implementing an option to turn emmet on for filetypes or if some people want it everywhere (thats how I have it in vim) would be nice and could solve this one and for all.
This option should be triggerable by plugins implementing new filetypes as well. Otherwise it would not be possible to fix issues like https://github.com/mat-mcloughlin/vscode-elixir/issues/11 since you can't just associate .eex with html or would it?
I did an ugly hack to force it enable:
# sed -i.bak 's/emmetSupportedModes=\["html",/emmetSupportedModes=\["nunjucks","html",/' /usr/share/code/resources/app/out/vs/workbench/workbench.main.js
Everything seems fine for now.
@mrmlnc what would be a good way to expose this that is consistent with emmet. Now that VS Code exposes emmet preferences and profiles?
@egamma, now it is difficult to say something.
The main problem is that we have a list of language identifiers that cannot be edited and supplemented. This imposes on us the responsibility to resolve issues such as this.
Now our main problem is the isEmmetEnabledMode function.
I see several ways to solve this problem.
Expand the list of language identifiers use emmet.syntaxProfiles property. For example:
Editor settings
{
"emmet.syntaxProfiles": {
// force HTML profile for Nunjucks syntax
"nunjucks": "html"
}
}
getSyntax function
// Supported by Emmet languages
const baseEmmetLanguages = ['html', 'css', 'xml', 'svg', 'xsl', 'haml', 'jade', 'jsx', 'slim', 'scss', 'sass', 'less', 'stylus', 'styl'];
public getSyntax(): string {
let position = this.editor.getSelection().getStartPosition();
let modeId = this.editor.getModel().getModeIdAtPosition(position.lineNumber, position.column);
let syntax = modeId.split('.').pop();
// Pseudo code
let syntaxProfilesLanguages = editor.settings.emmet.syntaxProfiles;
if (syntaxProfilesLanguages[syntax] && typeof syntaxProfilesLanguages[syntax] === 'string') {
return syntaxProfilesLanguages[syntax];
}
// Default checks for `typescriptreact`, `sass-indented` and other IDs
// ...
return syntax;
}
Pros
Cons
typescriptreact, sass-indented and etc).This method solves the problem fundamentally.
Each language has a chain of heritage in tmLanguage or language.json. Also the scope is specified in the package.json file in each language extension (contributes.grammars.scopeName). This parameter is stored in the scopeName field.
scopeName (
line 1) — this should be a unique name for the grammar, following the convention of being a dot-separated name where each new (left-most) part specializes the name. Normally it would be a two-part name where the first is eithertextorsourceand the second is the name of the language or document type. But if you are specializing an existing type, you probably want to derive the name from the type you are specializing. For example Markdown istext.html.markdownand Ruby on Rails (rhtml files) istext.html.rails. The advantage of deriving it from (in this case)text.htmlis that everything which works in the text.html scope will also work in thetext.html.«something»scope (but with a lower precedence than something specifically targetingtext.html.«something»).Documentation: https://manual.macromates.com/en/language_grammars
For example, this method is used in Atom. Thus we can 100% be sure that Emmet works for all languages, that he can support.
Warning
Unfortunately, some languages do not have this field. See code example below (razor).
For example with pesudo code:
// Supported by Emmet languages
const baseEmmetLanguages = ['html', 'css', 'xml', 'svg', 'xsl', 'haml', 'jade', 'jsx', 'slim', 'scss', 'sass', 'less', 'stylus', 'styl'];
public isEmmetEnabledMode(): boolean {
let syntax = this.getSyntax();
return Boolean(syntax);
}
public getSyntax(): string {
// Pseudo code
let scopeName = getDocumentSyntaxScopeName();
// scope[0] -> source or text
// scope[1] -> parent syntax
// ...
// scope[n] -> current syntax
let scopes = scopeName.split('.')
// For example:
//
// [1] Less -> source.css.less
// [2] CSS -> source.css
// [3] Java -> source.java
// [4] PHP -> text.html.php
// [5] ASP vb.NET -> source.asp.vb.net
let currentSyntax = scopes[scopes.length];
// [1] Less -> source.css.less -> less -> true -> return `less`
// [2] CSS -> source.css -> css -> true -> return `css`
// [3] Java -> source.java -> java -> false
// [4] PHP -> text.html.php -> php -> false
// [5] ASP vb.NET -> source.asp.vb.net -> net -> false
if (baseEmmetLanguages.indexOf(currentSyntax) !== -1) {
return currentSyntax;
}
let parentSyntax = scopes[1];
// [3] Java -> source.java -> java -> false
// [4] PHP -> text.html.php -> html -> true -> return `html`
// [5] ASP vb.NET -> source.asp.vb.net -> asp -> false
if (baseEmmetLanguages.indexOf(parentSyntax) !== -1) {
return parentSyntax;
}
// Default checks for `typescriptreact`, `sass-indented` and other IDs
//
// Handlebars (hbs), erb, ejs, twig with extension?
if (/\b(razor)\b/.test(syntax)) { // treat like html
return 'html';
}
if (/\b(typescriptreact|javascriptreact)\b/.test(syntax)) { // treat like tsx like jsx
return 'jsx';
}
if (syntax === 'sass-indented') { // map sass-indented to sass
return 'sass';
}
// [3] Java -> source.java -> return null -> drop Emmet
// [5] ASP vb.NET -> source.asp.vb.net -> return null -> drop Emmet
return ''; // may be null or false ?
}
Pros
Cons
typescriptreact, sass-indented and etc).Not a problem, but need to optimize language checking. In the above code checks only the current and parent syntax. But in the real case, a set of syntaxes can be unlimited (see ASP vb.NET source.asp.vb.net).
For example, Atom, checks first only the current and parent syntax and, if not, then begins to check the entire chain: https://github.com/emmetio/emmet-atom/blob/master/lib/editor-proxy.coffee#L207-L223
@mrmlnc great analysis thanks. What is obvious is now that we support the syntaxProfiles setting the hard coded behaviour in isEmmetEnabledMode function must be removed.
I've added this on the plan for August since we are just wrapping up the July update.
@mrmlnc starting to implement this. I think we need both solutions that you described above:
@egamma, on one hand I have nothing against current solution (partial fix). But on the other hand, when I install extension (for example, HAML), I want to support Emmet out of the box. Also users must now have additional settings. And we still have a list of available languages.
And to make matters worse, users should be aware of the existing profiles Emmet, that are not described in the documentation.
@mrmlnc I fully agree with you. This is why I didn't close the issue yet. I'm about to check in the 2nd part of the fix which covers the NunJucks scenario.
And to make matters worse, users should be aware of the existing profiles Emmet, that are not described in the documentation.
I've filed this issue to track the documentation gap https://github.com/Microsoft/vscode-docs/issues/541. Is there some emmet documentation we can refer to?
@egamma the official documentation not contain a list of available profiles. It can be found in the lib/snippets.json file. Also see http://docs.emmet.io/abbreviations/types/#element-types
csshtmlxhtml - hardcode (the same as html, but outputs empty elements with closed slash: <br />.)xmlsvgxslhamljadejsxslimscsssasslessstylusstyl = stylusAs array:
['html', 'xhtml', 'css', 'xml', 'xsl', 'haml', 'jade', 'jsx', 'slim', 'scss', 'sass', 'less', 'stylus', 'styl']
@egamma, small question:
First, see https://github.com/Microsoft/vscode/commit/2f2d6502638fa108f8ddbcc5831b38a35d5a1c11#commitcomment-18768402
Also maybe add a condition for those languages that are already present in the list? It seems to me there is no point in checking the parent languages for CSS or HTML.
public getSyntax(): string {
...
// user can overwrite the syntax using the emmet syntaxProfiles setting
let profile = this.getSyntaxProfile(syntax);
if (profile) {
return profile;
}
+ if (this.emmetSupportedModes.indexOf(syntax) !== -1) {
+ return syntax;
+ }
if (/\b(razor|handlebars|erb|php|hbs|ejs|twig)\b/.test(syntax)) { // treat like html
IMO, you can replace emmetSupportedModes array on array of officially supported languages by Emmet and remove twig, ejs, erb from treat like html condition.
PR ? :smile_cat:
@mrmlnc thanks for the code review.
Are you sure that you correctly get the parent language?
No, I wasn't aware of this case text.html.php.laravel-blade and I assumed going one specialization level to the left is sufficient. In this case let's not call it the 'parent language' but the 'document language.' This is consistent with text mate documentation
"... first is either text or source and the second is the name of the language or document type."
However, it is really time for some unit tests for the getSyntax() code. I'll start on it.
Also maybe add a condition for those languages that are already present in the list?
👍
IMO, you can replace emmetSupportedModes array on array of officially supported languages by Emmet and remove twig , ejs , erb from treat like html condition.
💯 , I wasn't sure of the official supported list. So a PR would be much appreciated.
@egamma, Sadly, TextMate does not limit the nesting of languages (but recommends use (source|text).(document type|language).language pattern).
As I understand from the documentation, scopeNames is formed according to the principle: On the right is a higher abstraction. For example, text.html.php.laravel-blade or source.asp.vb.net.
What do you think about replacing the last condition on cycle? It's completely cover scopeNames (see https://github.com/emmetio/emmet-atom/blob/master/lib/editor-proxy.coffee#L218-L221).
private checkParentMode(syntax: string): string {
...
- let parentMode = languages[languages.length-2];
- if (this.emmetSupportedModes.indexOf(parentMode) !== -1) {
- return parentMode;
- }
return syntax;
}
for (let i = 1; i < languages.length; i++) {
const language = languages[languages.length - i];
if (this.emmetSupportedModes.indexOf(language) !== -1) {
return language;
}
}
@mrmlnc :+1: on this change, it makes the core more robust and removes an assumption.
BTW, I'm refactoring the editorAccessor code a bit to make it easier testable. So if you plan to make a PR, please wait until I'm done with this refactoring and we can have unit tests for all this logic.
@egamma,
So if you plan to make a PR, please wait until I'm done with this refactoring...
Yes, thanks for the warning. I'll wait your changes.
@mrmlnc
I've checked in unit test for the EditorAccessor, please review them and add more as needed.
@egamma, done.
Hello folks!
I have a eRuby file myFile.html.erb and it seems that emmet does not work.
I also tried to add this to settings but no chance.
{
"emmet.syntaxProfiles": {
"eruby": "html"
}
}
Also tried erb no chance.
How can I fix this?
Thanks in advance!
@iamandrewluca commenting on a closed issue can easily be overlooked. Please file a new issue for the problem you mention in https://github.com/Microsoft/vscode/issues/9500#issuecomment-269376269
fyi @ramya-rao-a this could be related to #18434
@iamandrewluca What is the language mode that appears in the bottom right corner of the status bar when you have this myFile.html.erb file open? My guess is that it would be plain text. I don't believe erb or eruby is one of the valid language modes in VS Code out of the box.
Set the language mode for this file to html and you should be good to go
@ramya-rao-a I am doing .vue files and I know switching to html in the Language Mode will actually fix my issue
but,
What is the consequences of that? What happen if the .vue extension have multiple processing like emmet itself have?
@yordis I don't understand what you mean by "multiple processing like emmet".
Did you try using emmet.syntaxProfiles setting to map vue to html?
I was also having trouble getting emmet to run on erb files. The language was mode was being recognized as erb after installing [vscode-ruby-erb](https://github.com/vortizhe/vscode-ruby-erb, and I had emmet.syntaxProfiles set to associate erb with html ("emmet.syntaxProfiles": { "erb": "html" }).
I had to disable useNewEmmet for it to work.
@hogdogthegod In the new emmet model, we are using emmet.includeLanguages for mapping new language modes to an existing language that has emmet support.
"syntaxProfiles" in emmet was meant to configure the output profile eg: casing control on attribute name or single/double quotes for attributes etc. You can read more here: https://docs.emmet.io/customization/syntax-profiles/
So we are moving away from hijacking "syntaxProfiles" for mapping new languages
Please read https://code.visualstudio.com/updates/v1_13#_emmet-abbreviation-expansion-in-suggestion-list and https://code.visualstudio.com/updates/v1_14#_emmet-abbreviation-improvements for more information on the new emmet model
You can simple use this:
"emmet.includeLanguages": {
"erb": "html"
}
Most helpful comment
@mrmlnc great analysis thanks. What is obvious is now that we support the syntaxProfiles setting the hard coded behaviour in
isEmmetEnabledModefunction must be removed.I've added this on the plan for August since we are just wrapping up the July update.