From b66b9871a0ee14a052d08c84530c3226fb705ee6 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 14 Sep 2016 11:11:45 +0200 Subject: [PATCH] [html] enable handlebars --- extensions/html/client/src/htmlMain.ts | 15 +++++++++++ .../server/src/service/parser/htmlScanner.ts | 25 ++++++++++++++++--- .../src/service/test/completion.test.ts | 11 ++++++++ src/vs/editor/editor.main.ts | 1 + src/vs/languages/languages.main.ts | 1 - 5 files changed, 49 insertions(+), 4 deletions(-) diff --git a/extensions/html/client/src/htmlMain.ts b/extensions/html/client/src/htmlMain.ts index 303d87b7a2f..5453ea343fd 100644 --- a/extensions/html/client/src/htmlMain.ts +++ b/extensions/html/client/src/htmlMain.ts @@ -62,4 +62,19 @@ export function activate(context: ExtensionContext) { } ], }); + + languages.setLanguageConfiguration('handlebars', { + wordPattern: /("(?:[^\\\"]*(?:\\.)?)*"?)|('(?:[^\\\']*(?:\\.)?)*'?)|[^\s<>={}\[\],]+/, + onEnterRules:[ + { + beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'), + afterText: /^<\/([_:\w][_:\w-.\d]*)\s*>$/i, + action: { indentAction: IndentAction.IndentOutdent } + }, + { + beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'), + action: { indentAction: IndentAction.Indent } + } + ], + }); } \ No newline at end of file diff --git a/extensions/html/server/src/service/parser/htmlScanner.ts b/extensions/html/server/src/service/parser/htmlScanner.ts index 2bb5c33ac7b..b6ea9566b5d 100644 --- a/extensions/html/server/src/service/parser/htmlScanner.ts +++ b/extensions/html/server/src/service/parser/htmlScanner.ts @@ -206,6 +206,10 @@ export interface Scanner { getScannerState(): ScannerState; } +const htmlScriptContents = { + 'text/x-handlebars-template': true +}; + export function createScanner(input: string, initialOffset = 0, initialState: ScannerState = ScannerState.WithinContent) : Scanner { let stream = new MultiLineStream(input, initialOffset); @@ -216,6 +220,8 @@ export function createScanner(input: string, initialOffset = 0, initialState: Sc let hasSpaceAfterTag: boolean; let lastTag: string; + let lastAttributeName: string; + let lastTypeValue: string; function nextElementName(): string { return stream.advanceIfRegExp(/^[_:\w][_:\w-.\d]*/).toLowerCase(); @@ -299,6 +305,8 @@ export function createScanner(input: string, initialOffset = 0, initialState: Sc break; case ScannerState.AfterOpeningStartTag: lastTag = nextElementName(); + lastTypeValue = null; + lastAttributeName = null; if (lastTag.length > 0) { hasSpaceAfterTag = false; state = ScannerState.WithinTag; @@ -316,8 +324,8 @@ export function createScanner(input: string, initialOffset = 0, initialState: Sc return finishToken(offset, TokenType.Whitespace); } if (hasSpaceAfterTag) { - let name = nextAttributeName(); - if (name.length > 0) { + lastAttributeName = nextAttributeName(); + if (lastAttributeName.length > 0) { state = ScannerState.AfterAttributeName; hasSpaceAfterTag = false; return finishToken(offset, TokenType.AttributeName); @@ -329,7 +337,12 @@ export function createScanner(input: string, initialOffset = 0, initialState: Sc } if (stream.advanceIfChar(_RAN)) { // > if (lastTag === 'script') { - state = ScannerState.WithinScriptContent; + if (lastTypeValue && htmlScriptContents[lastTypeValue]) { + // stay in html + state = ScannerState.WithinContent; + } else { + state = ScannerState.WithinScriptContent; + } } else if (lastTag === 'style') { state = ScannerState.WithinStyleContent; } else { @@ -357,6 +370,9 @@ export function createScanner(input: string, initialOffset = 0, initialState: Sc } let attributeValue = stream.advanceIfRegExp(/^[^\s"'`=<>]+/); if (attributeValue.length > 0) { + if (lastAttributeName === 'type') { + lastTypeValue = attributeValue; + } state = ScannerState.WithinTag; hasSpaceAfterTag = false; return finishToken(offset, TokenType.AttributeValue); @@ -367,6 +383,9 @@ export function createScanner(input: string, initialOffset = 0, initialState: Sc if (stream.advanceUntilChar(ch)) { stream.advance(1); // consume quote } + if (lastAttributeName === 'type') { + lastTypeValue = stream.getSource().substring(offset + 1, stream.pos() - 1); + } state = ScannerState.WithinTag; hasSpaceAfterTag = false; return finishToken(offset, TokenType.AttributeValue); diff --git a/extensions/html/server/src/service/test/completion.test.ts b/extensions/html/server/src/service/test/completion.test.ts index 04237fe03f3..f369bb3f408 100644 --- a/extensions/html/server/src/service/test/completion.test.ts +++ b/extensions/html/server/src/service/test/completion.test.ts @@ -269,6 +269,17 @@ suite('HTML Completion', () => { ], testDone); }); + suite('Handlevar Completion', (testDone) => { + run([ + + testCompletionFor('' , { + items: [ + { label: 'div', resultText: '' }, + ] + }) + ], testDone); + }); + test('Intellisense aria', function (testDone): any { let expectedAriaAttributes = [ { label: 'aria-activedescendant' }, diff --git a/src/vs/editor/editor.main.ts b/src/vs/editor/editor.main.ts index 0fbf6472d3b..dbbf54ef04e 100644 --- a/src/vs/editor/editor.main.ts +++ b/src/vs/editor/editor.main.ts @@ -12,6 +12,7 @@ import 'vs/editor/contrib/quickOpen/browser/quickCommand'; import 'vs/languages/languages.main'; import 'vs/languages/php/common/php.contribution'; import 'vs/languages/html/common/html.contribution'; +import 'vs/languages/handlebars/common/handlebars.contribution'; import {createMonacoBaseAPI} from 'vs/editor/common/standalone/standaloneBase'; import {createMonacoEditorAPI} from 'vs/editor/browser/standalone/standaloneEditor'; diff --git a/src/vs/languages/languages.main.ts b/src/vs/languages/languages.main.ts index c8d49d4dda4..ecaf274b2af 100644 --- a/src/vs/languages/languages.main.ts +++ b/src/vs/languages/languages.main.ts @@ -6,5 +6,4 @@ 'use strict'; -import 'vs/languages/handlebars/common/handlebars.contribution'; import 'vs/languages/razor/common/razor.contribution';