提交 593ecbef 编写于 作者: R Ramya Rao 提交者: GitHub

Deprecating old emmet (#31783)

* Deprecate old emmet

* Update translations
上级 f4871bff
......@@ -22,7 +22,7 @@
"command.decrementNumberByTen": "Decrement by 10",
"emmetSyntaxProfiles": "Define profile for specified syntax or use your own profile with specific rules.",
"emmetExclude": "An array of languages where emmet abbreviations should not be expanded.",
"emmetExtensionsPath": "Path to a folder containing emmet profiles, snippets and preferences. Only profiles are honored from extensions path when emmet.useNewEmmet is set to true.'",
"emmetExtensionsPath": "Path to a folder containing emmet profiles and snippets.'",
"emmetShowExpandedAbbreviation": "Shows expanded emmet abbreviations as suggestions.\nThe option \"inMarkupAndStylesheetFilesOnly\" applies to html, haml, jade, slim, xml, xsl, css, scss, sass, less and stylus.\nThe option \"always\" applies to all parts of the file regardless of markup/css.",
"emmetShowAbbreviationSuggestions": "Shows possible emmet abbreviations as suggestions. Not applicable in stylesheets or when emmet.showExpandedAbbreviation is set to \"never\".",
"emmetIncludeLanguages": "Enable emmet abbreviations in languages that are not supported by default. Add a mapping here between the language and emmet supported language.\n Eg: {\"vue-html\": \"html\", \"javascript\": \"javascriptreact\"}",
......
......@@ -255,7 +255,7 @@ export function sameNodes(node1: Node, node2: Node): boolean {
export function getEmmetConfiguration() {
const emmetConfig = vscode.workspace.getConfiguration('emmet');
return {
useNewEmmet: emmetConfig['useNewEmmet'],
useNewEmmet: true,
showExpandedAbbreviation: emmetConfig['showExpandedAbbreviation'],
showAbbreviationSuggestions: emmetConfig['showAbbreviationSuggestions'],
syntaxProfiles: emmetConfig['syntaxProfiles'],
......
......@@ -87,11 +87,6 @@
"from": "debug@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz"
},
"emmet": {
"version": "1.3.1",
"from": "ramya-rao-a/emmet#vscode",
"resolved": "git+https://github.com/ramya-rao-a/emmet.git#b4d3edc30de7fa98032302cd1f017a940ab879fd"
},
"expand-brackets": {
"version": "0.1.5",
"from": "expand-brackets@>=0.1.4 <0.2.0",
......
......@@ -4,43 +4,22 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import nls = require('vs/nls');
import { BasicEmmetEditorAction } from 'vs/workbench/parts/emmet/electron-browser/emmetActions';
import { EmmetEditorAction } from 'vs/workbench/parts/emmet/electron-browser/emmetActions';
import { editorAction } from 'vs/editor/common/editorCommonExtensions';
import { ICommonCodeEditor } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { CoreEditingCommands } from 'vs/editor/common/controller/coreCommands';
import { KeyCode } from 'vs/base/common/keyCodes';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
@editorAction
class ExpandAbbreviationAction extends BasicEmmetEditorAction {
class ExpandAbbreviationAction extends EmmetEditorAction {
constructor() {
super(
'editor.emmet.action.expandAbbreviation',
nls.localize('expandAbbreviationAction', "Emmet: Expand Abbreviation"),
'Emmet: Expand Abbreviation',
'expand_abbreviation',
{
primary: KeyCode.Tab,
kbExpr: ContextKeyExpr.and(
EditorContextKeys.textFocus,
EditorContextKeys.hasOnlyEmptySelection,
EditorContextKeys.hasSingleSelection,
EditorContextKeys.tabDoesNotMoveFocus,
ContextKeyExpr.has('config.emmet.triggerExpansionOnTab'),
ContextKeyExpr.not('config.emmet.useNewEmmet')
)
}
);
}
super({
id: 'editor.emmet.action.expandAbbreviation',
label: nls.localize('expandAbbreviationAction', "Emmet: Expand Abbreviation"),
alias: 'Emmet: Expand Abbreviation',
precondition: EditorContextKeys.writable,
actionName: 'expand_abbreviation'
});
protected noExpansionOccurred(editor: ICommonCodeEditor): void {
// forward the tab key back to the editor
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
}
}
}
\ No newline at end of file
......@@ -6,11 +6,10 @@
'use strict';
import nls = require('vs/nls');
import { EmmetEditorAction, EmmetActionContext } from 'vs/workbench/parts/emmet/electron-browser/emmetActions';
import { EmmetEditorAction } from 'vs/workbench/parts/emmet/electron-browser/emmetActions';
import { ServicesAccessor, editorAction } from 'vs/editor/common/editorCommonExtensions';
import { editorAction } from 'vs/editor/common/editorCommonExtensions';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { IQuickOpenService, IInputOptions } from 'vs/platform/quickOpen/common/quickOpen';
@editorAction
class WrapWithAbbreviationAction extends EmmetEditorAction {
......@@ -25,21 +24,4 @@ class WrapWithAbbreviationAction extends EmmetEditorAction {
});
}
public runEmmetAction(accessor: ServicesAccessor, ctx: EmmetActionContext) {
const quickOpenService = accessor.get(IQuickOpenService);
let options: IInputOptions = {
prompt: nls.localize('enterAbbreviation', "Enter Abbreviation"),
placeHolder: nls.localize('abbreviation', "Abbreviation")
};
quickOpenService.input(options).then(abbreviation => {
this.wrapAbbreviation(ctx, abbreviation);
});
}
private wrapAbbreviation(ctx: EmmetActionContext, abbreviation: string) {
if (abbreviation && !ctx.emmet.run('wrap_with_abbreviation', ctx.editorAccessor, abbreviation)) {
this.noExpansionOccurred(ctx.editor);
}
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ICommonCodeEditor } from 'vs/editor/common/editorCommon';
import strings = require('vs/base/common/strings');
import { Range } from 'vs/editor/common/core/range';
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
import { Position } from 'vs/editor/common/core/position';
import { CoreEditingCommands } from 'vs/editor/common/controller/coreCommands';
import { SnippetParser, Placeholder, Variable, Text, Marker } from 'vs/editor/contrib/snippet/browser/snippetParser';
import emmet = require('emmet');
export interface IGrammarContributions {
getGrammar(mode: string): string;
}
export interface ILanguageIdentifierResolver {
getLanguageIdentifier(modeId: LanguageId): LanguageIdentifier;
}
export class EditorAccessor implements emmet.Editor {
private _languageIdentifierResolver: ILanguageIdentifierResolver;
private _editor: ICommonCodeEditor;
private _syntaxProfiles: any;
private _excludedLanguages: any;
private _grammars: IGrammarContributions;
private _emmetActionName: string;
private _hasMadeEdits: boolean;
private readonly emmetSupportedModes = ['html', 'css', 'xml', 'xsl', 'haml', 'jade', 'jsx', 'slim', 'scss', 'sass', 'less', 'stylus', 'styl', 'svg'];
constructor(languageIdentifierResolver: ILanguageIdentifierResolver, editor: ICommonCodeEditor, syntaxProfiles: any, excludedLanguages: String[], grammars: IGrammarContributions, emmetActionName?: string) {
this._languageIdentifierResolver = languageIdentifierResolver;
this._editor = editor;
this._syntaxProfiles = syntaxProfiles;
this._excludedLanguages = excludedLanguages;
this._hasMadeEdits = false;
this._grammars = grammars;
this._emmetActionName = emmetActionName;
}
public getEmmetSupportedModes(): string[] {
return this.emmetSupportedModes;
}
public isEmmetEnabledMode(): boolean {
return this.emmetSupportedModes.indexOf(this.getSyntax()) !== -1;
}
public getSelectionRange(): emmet.Range {
let selection = this._editor.getSelection();
return {
start: this.getOffsetFromPosition(selection.getStartPosition()),
end: this.getOffsetFromPosition(selection.getEndPosition())
};
}
public getCurrentLineRange(): emmet.Range {
let currentLine = this._editor.getSelection().startLineNumber;
return {
start: this.getOffsetFromPosition(new Position(currentLine, 1)),
end: this.getOffsetFromPosition(new Position(currentLine + 1, 1))
};
}
public getCaretPos(): number {
let selectionStart = this._editor.getSelection().getStartPosition();
return this.getOffsetFromPosition(selectionStart);
}
public setCaretPos(pos: number): void {
this.createSelection(pos);
}
public getCurrentLine(): string {
let selectionStart = this._editor.getSelection().getStartPosition();
return this._editor.getModel().getLineContent(selectionStart.lineNumber);
}
public onBeforeEmmetAction(): void {
this._hasMadeEdits = false;
}
public replaceContent(value: string, start: number, end: number, no_indent: boolean): void {
let range = this.getRangeToReplace(value, start, end);
if (!range) {
return;
}
const tweakedValue = EditorAccessor.fixEmmetFinalTabstop(value);
// let selection define the typing range
this._editor.setSelection(range);
SnippetController2.get(this._editor).insert(tweakedValue);
// let codeSnippet = snippets.CodeSnippet.fromEmmet(value);
// SnippetController.get(this._editor).runWithReplaceRange(codeSnippet, range);
}
private static fixEmmetFinalTabstop(template: string): string {
let matchFinalStops = template.match(/\$\{0\}|\$0/g);
if (!matchFinalStops || matchFinalStops.length === 1) {
return template;
}
// string to string conversion that tries to fix the
// snippet in-place
let snippet = new SnippetParser().parse(template);
let maxIndex = -Number.MIN_VALUE;
// find highest placeholder index
snippet.walk(candidate => {
if (candidate instanceof Placeholder) {
let index = candidate.index;
if (index > maxIndex) {
maxIndex = index;
}
}
return true;
});
// rewrite final tabstops
snippet.walk(candidate => {
if (candidate instanceof Placeholder) {
if (candidate.isFinalTabstop) {
candidate.index = ++maxIndex;
}
}
return true;
});
// write back as string
function toSnippetString(marker: Marker): string {
if (marker instanceof Text) {
return SnippetParser.escape(marker.value);
} else if (marker instanceof Placeholder) {
if (marker.choice) {
return `\${${marker.index}|${marker.choice.options.map(toSnippetString).join(',')}|}`;
} else if (marker.children.length > 0) {
return `\${${marker.index}:${marker.children.map(toSnippetString).join('')}}`;
} else {
return `\$${marker.index}`;
}
} else if (marker instanceof Variable) {
if (marker.children.length > 0) {
return `\${${marker.name}:${marker.children.map(toSnippetString).join('')}}`;
} else {
return `\${${marker.name}}`;
}
} else {
throw new Error('unexpected marker: ' + marker);
}
}
return snippet.children.map(toSnippetString).join('');
}
public getRangeToReplace(value: string, start: number, end: number): Range {
//console.log('value', value);
let startPosition = this.getPositionFromOffset(start);
let endPosition = this.getPositionFromOffset(end);
// test if < or </ are located before or > after the replace range. Either replace these too, or block the expansion
var currentLine = this._editor.getModel().getLineContent(startPosition.lineNumber).substr(0, startPosition.column - 1); // content before the replaced range
var match = currentLine.match(/<[/]?$/);
if (match) {
if (strings.startsWith(value, match[0])) {
startPosition = new Position(startPosition.lineNumber, startPosition.column - match[0].length);
} else {
return null; // ignore
}
}
// test if > is located after the replace range. Either replace these too, or block the expansion
if (this._editor.getModel().getLineContent(endPosition.lineNumber).substr(endPosition.column - 1, endPosition.column) === '>') {
if (strings.endsWith(value, '>')) {
endPosition = new Position(endPosition.lineNumber, endPosition.column + 1);
} else {
return null; // ignore
}
}
// If this is the first edit in this "transaction", push an undo stop before them
if (!this._hasMadeEdits) {
this._hasMadeEdits = true;
this._editor.pushUndoStop();
}
let range = new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column);
let textToReplace = this._editor.getModel().getValueInRange(range);
// During Expand Abbreviation action, if the expanded abbr is the same as the text it intends to replace,
// then treat it as a no-op and return TAB to the editor
if (this._emmetActionName === 'expand_abbreviation' && (value === textToReplace || value === textToReplace + '${0}')) {
CoreEditingCommands.Tab.runEditorCommand(null, this._editor, null);
return null;
}
return range;
}
public onAfterEmmetAction(): void {
// If there were any edits in this "transaction", push an undo stop after them
if (this._hasMadeEdits) {
this._editor.pushUndoStop();
}
}
public getContent(): string {
return this._editor.getModel().getValue();
}
public createSelection(startOffset: number, endOffset?: number): void {
let startPosition = this.getPositionFromOffset(startOffset);
let endPosition = null;
if (!endOffset) {
endPosition = startPosition;
} else {
endPosition = this.getPositionFromOffset(endOffset);
}
let range = new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column);
this._editor.setSelection(range);
this._editor.revealRange(range);
}
public getSyntax(): string {
return this.getSyntaxInternal(true);
}
public getSyntaxInternal(overrideUsingProfiles: boolean): string {
let position = this._editor.getSelection().getStartPosition();
this._editor.getModel().forceTokenization(position.lineNumber);
let languageId = this._editor.getModel().getLanguageIdAtPosition(position.lineNumber, position.column);
let language = this._languageIdentifierResolver.getLanguageIdentifier(languageId).language;
let syntax = language.split('.').pop();
if (this._excludedLanguages.indexOf(syntax) !== -1) {
return '';
}
// user can overwrite the syntax using the emmet syntaxProfiles setting
let profile = this.getSyntaxProfile(syntax);
if (overrideUsingProfiles && profile && this.emmetSupportedModes.indexOf(profile) !== -1) {
return profile;
}
if (this.emmetSupportedModes.indexOf(syntax) !== -1) {
return syntax;
}
if (/\b(typescriptreact|javascriptreact|jsx-tags)\b/.test(syntax)) { // treat tsx like jsx
return 'jsx';
}
if (syntax === 'sass-indented') { // map sass-indented to sass
return 'sass';
}
syntax = this.checkParentMode(syntax);
return syntax;
}
public getLanguage() {
let position = this._editor.getSelection().getStartPosition();
this._editor.getModel().forceTokenization(position.lineNumber);
let languageId = this._editor.getModel().getLanguageIdAtPosition(position.lineNumber, position.column);
let language = this._languageIdentifierResolver.getLanguageIdentifier(languageId).language;
let syntax = language.split('.').pop();
return {
language: syntax,
parentMode: this.checkParentMode(syntax)
};
}
private getSyntaxProfile(syntax: string): string {
const profile = this._syntaxProfiles[syntax];
if (profile && typeof profile === 'string') {
return profile;
}
return undefined;
}
private checkParentMode(syntax: string): string {
let languageGrammar = this._grammars.getGrammar(syntax);
if (!languageGrammar) {
return syntax;
}
let languages = languageGrammar.split('.');
if (languages.length < 2) {
return syntax;
}
for (let i = 1; i < languages.length; i++) {
const language = languages[languages.length - i];
if (this.emmetSupportedModes.indexOf(language) !== -1) {
return language;
}
}
return syntax;
}
// If users have created their own output profile for current syntax as described
// http://docs.emmet.io/customization/syntax-profiles/#create-your-own-profile
// then we return the name of this profile. Else, we send null and
// emmet is smart enough to guess the right output profile
public getProfileName(): string {
let syntax = this.getSyntaxInternal(false);
const profile = this._syntaxProfiles[syntax];
if (profile && typeof profile !== 'string') {
return syntax;
}
return null;
}
public prompt(title: string): any {
//
}
public getSelection(): string {
let selection = this._editor.getSelection();
let model = this._editor.getModel();
let start = selection.getStartPosition();
let end = selection.getEndPosition();
let range = new Range(start.lineNumber, start.column, end.lineNumber, end.column);
return model.getValueInRange(range);
}
public getFilePath(): string {
return this._editor.getModel().uri.fsPath;
}
private getPositionFromOffset(offset: number): Position {
return this._editor.getModel().getPositionAt(offset);
}
private getOffsetFromPosition(position: Position): number {
return this._editor.getModel().getOffsetAt(position);
}
}
......@@ -5,36 +5,6 @@
'use strict';
import nls = require('vs/nls');
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import './actions/expandAbbreviation';
import './actions/wrapWithAbbreviation';
// Configuration: emmet
const configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'emmet',
'order': 6,
'title': nls.localize('emmetConfigurationTitle', "Emmet"),
'type': 'object',
'properties': {
'emmet.triggerExpansionOnTab': {
'type': 'boolean',
'default': true,
'description': nls.localize('triggerExpansionOnTab', "When enabled, emmet abbreviations are expanded when pressing TAB. Not applicable when emmet.useNewemmet is set to true.")
},
'emmet.preferences': {
'type': 'object',
'default': {},
'description': nls.localize('emmetPreferences', "Preferences used to modify behavior of some actions and resolvers of Emmet. Not applicable when emmet.useNewemmet is set to true.")
},
'emmet.useNewEmmet': {
'type': 'boolean',
'default': true,
'description': nls.localize('useNewEmmet', 'Try out the new emmet modules (which will eventually replace the old single emmet library) for all emmet features.')
}
}
});
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'emmet' {
export interface Range {
start: number;
end: number;
}
export interface Preferences {
reset();
}
export interface Profiles {
reset();
}
export interface Editor {
/**
* Returns character indexes of selected text: object with <code>start</code>
* and <code>end</code> properties. If there's no selection, should return
* object with <code>start</code> and <code>end</code> properties referring
* to current caret position
* @return {Object}
* @example
* var selection = editor.getSelectionRange();
* alert(selection.start + ', ' + selection.end);
*/
getSelectionRange(): Range;
/**
* Creates selection from <code>start</code> to <code>end</code> character
* indexes. If <code>end</code> is omitted, this method should place caret
* and <code>start</code> index
* @param {Number} start
* @param {Number} [end]
* @example
* editor.createSelection(10, 40);
*
* //move caret to 15th character
* editor.createSelection(15);
*/
createSelection(start: number, end?: number): void;
/**
* Returns current line's start and end indexes as object with <code>start</code>
* and <code>end</code> properties
* @return {Object}
* @example
* var range = editor.getCurrentLineRange();
* alert(range.start + ', ' + range.end);
*/
getCurrentLineRange(): Range;
/**
* Returns current caret position
* @return {Number|null}
*/
getCaretPos(): number;
/**
* Set new caret position
* @param {Number} pos Caret position
*/
setCaretPos(pos: number): void;
/**
* Returns content of current line
* @return {String}
*/
getCurrentLine(): string;
/**
* Replace editor's content or it's part (from <code>start</code> to
* <code>end</code> index). If <code>value</code> contains
* <code>caret_placeholder</code>, the editor will put caret into
* this position. If you skip <code>start</code> and <code>end</code>
* arguments, the whole target's content will be replaced with
* <code>value</code>.
*
* If you pass <code>start</code> argument only,
* the <code>value</code> will be placed at <code>start</code> string
* index of current content.
*
* If you pass <code>start</code> and <code>end</code> arguments,
* the corresponding substring of current target's content will be
* replaced with <code>value</code>.
* @param {String} value Content you want to paste
* @param {Number} [start] Start index of editor's content
* @param {Number} [end] End index of editor's content
* @param {Boolean} [no_indent] Do not auto indent <code>value</code>
*/
replaceContent(value: string, start: number, end: number, no_indent: boolean): void;
/**
* Returns editor's content
* @return {String}
*/
getContent(): string;
/**
* Returns current editor's syntax mode
* @return {String}
*/
getSyntax(): string;
/**
* Returns current output profile name (see profile module).
* In most cases, this method should return <code>null</code> and let
* Emmet guess best profile name for current syntax and user data.
* In case you’re using advanced editor with access to syntax scopes
* (like Sublime Text 2), you can return syntax name for current scope.
* For example, you may return `line` profile when editor caret is inside
* string of programming language.
*
* @return {String}
*/
getProfileName(): string;
/**
* Ask user to enter something
* @param {String} title Dialog title
* @return {String} Entered data
*/
prompt(title: string): string;
getSelection(): string;
/**
* Returns current editor's file path
* @return {String}
*/
getFilePath(): string;
}
/**
* Runs given action
* @param {String} name Action name
* @param {IEmmetEditor} editor Editor instance
* @return {Boolean} Returns true if action was performed successfully
*/
export function run(action: string, editor: Editor, arg?: string): boolean;
export const preferences: Preferences;
export const profile: Profiles;
/**
* Loads preferences from JSON object
*/
export function loadPreferences(preferences: any): void;
/**
* Loads named profiles from JSON object
*/
export function loadProfiles(profiles: any): void;
/**
* Loads user snippets and abbreviations.
*/
export function loadSnippets(snippets: any): void;
/**
* Resets all user-defined data: preferences, profiles and snippets
*/
export function resetUserData(): void;
}
......@@ -2,43 +2,29 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path="emmet.d.ts" />
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { ICommonCodeEditor } from 'vs/editor/common/editorCommon';
import { EditorAction, ServicesAccessor, IActionOptions, ICommandKeybindingsOptions } from 'vs/editor/common/editorCommonExtensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { EditorAction, ServicesAccessor, IActionOptions } from 'vs/editor/common/editorCommonExtensions';
import { grammarsExtPoint, ITMSyntaxExtensionPoint } from 'vs/workbench/services/textMate/electron-browser/TMGrammars';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { EditorAccessor, IGrammarContributions } from 'vs/workbench/parts/emmet/electron-browser/editorAccessor';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IExtensionService, ExtensionPointContribution } from 'vs/platform/extensions/common/extensions';
import { IMessageService } from 'vs/platform/message/common/message';
import * as emmet from 'emmet';
import * as path from 'path';
import * as pfs from 'vs/base/node/pfs';
import Severity from 'vs/base/common/severity';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ICommandService } from 'vs/platform/commands/common/commands';
interface IEmmetConfiguration {
emmet: {
preferences: any;
syntaxProfiles: any;
triggerExpansionOnTab: boolean,
excludeLanguages: string[],
extensionsPath: string,
useNewEmmet: boolean
};
}
import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
interface ModeScopeMap {
[key: string]: string;
}
export interface IGrammarContributions {
getGrammar(mode: string): string;
}
export interface ILanguageIdentifierResolver {
getLanguageIdentifier(modeId: LanguageId): LanguageIdentifier;
}
class GrammarContributions implements IGrammarContributions {
private static _grammars: ModeScopeMap = null;
......@@ -65,171 +51,6 @@ class GrammarContributions implements IGrammarContributions {
}
}
class LazyEmmet {
private static _INSTANCE = new LazyEmmet();
private static extensionsPath = '';
private static snippetsFromFile = {};
private static syntaxProfilesFromFile = {};
private static preferencesFromFile = {};
private static workspaceRoot = '';
private static emmetSupportedModes: string[];
public static withConfiguredEmmet(configurationService: IConfigurationService,
messageService: IMessageService,
telemetryService: ITelemetryService,
emmetSupportedModes: string[],
workspaceRoot: string,
callback: (_emmet: typeof emmet) => void): TPromise<void> {
LazyEmmet.workspaceRoot = workspaceRoot;
LazyEmmet.emmetSupportedModes = emmetSupportedModes;
return LazyEmmet._INSTANCE.withEmmetPreferences(configurationService, messageService, telemetryService, callback);
}
private _emmetPromise: TPromise<typeof emmet>;
private _messageService: IMessageService;
constructor() {
this._emmetPromise = null;
}
public withEmmetPreferences(configurationService: IConfigurationService,
messageService: IMessageService,
telemetryService: ITelemetryService,
callback: (_emmet: typeof emmet) => void): TPromise<void> {
return this._loadEmmet().then((_emmet: typeof emmet) => {
this._messageService = messageService;
this._withEmmetPreferences(configurationService, telemetryService, _emmet, callback);
}, (e) => {
callback(null);
});
}
private _loadEmmet(): TPromise<typeof emmet> {
if (!this._emmetPromise) {
this._emmetPromise = new TPromise<typeof emmet>((c, e) => {
require(['emmet'], c, e);
});
}
return this._emmetPromise;
}
private updateEmmetPreferences(configurationService: IConfigurationService,
telemetryService: ITelemetryService,
_emmet: typeof emmet): TPromise<any> {
let emmetPreferences = configurationService.getConfiguration<IEmmetConfiguration>().emmet;
let loadEmmetSettings = () => {
let syntaxProfiles = { ...LazyEmmet.syntaxProfilesFromFile, ...emmetPreferences.syntaxProfiles };
let preferences = { ...LazyEmmet.preferencesFromFile, ...emmetPreferences.preferences };
let snippets = LazyEmmet.snippetsFromFile;
let mappedModes = [];
let outputProfileFromSettings = false;
for (let key in emmetPreferences.syntaxProfiles) {
if (LazyEmmet.emmetSupportedModes.indexOf(key) === -1) {
mappedModes.push(key);
} else {
outputProfileFromSettings = true;
}
}
try {
_emmet.loadPreferences(preferences);
_emmet.loadProfiles(syntaxProfiles);
_emmet.loadSnippets(snippets);
let emmetCustomizationTelemetry = {
emmetPreferencesFromFile: Object.keys(LazyEmmet.preferencesFromFile).length > 0,
emmetSyntaxProfilesFromFile: Object.keys(LazyEmmet.syntaxProfilesFromFile).length > 0,
emmetSnippetsFromFile: Object.keys(LazyEmmet.snippetsFromFile).length > 0,
emmetPreferencesFromSettings: Object.keys(emmetPreferences.preferences).length > 0,
emmetSyntaxProfilesFromSettings: outputProfileFromSettings,
emmetMappedModes: mappedModes
};
telemetryService.publicLog('emmetCustomizations', emmetCustomizationTelemetry);
} catch (err) {
// ignore
}
};
// Whether loading the files was a success or not, we load emmet with what we have
return this.updateFromExtensionsPath(emmetPreferences.extensionsPath).then(loadEmmetSettings, (err) => {
// Errors from all the promises used to fetch/read dir/files would bubble up here
console.log(err);
loadEmmetSettings();
});
}
private updateFromExtensionsPath(extPath: string): TPromise<any> {
if (extPath !== LazyEmmet.extensionsPath) {
LazyEmmet.extensionsPath = extPath;
LazyEmmet.snippetsFromFile = {};
LazyEmmet.preferencesFromFile = {};
LazyEmmet.syntaxProfilesFromFile = {};
if (extPath && extPath.trim()) {
let dirPath = path.isAbsolute(extPath) ? extPath : path.join(LazyEmmet.workspaceRoot, extPath);
let snippetsPath = path.join(dirPath, 'snippets.json');
let syntaxProfilesPath = path.join(dirPath, 'syntaxProfiles.json');
let preferencesPath = path.join(dirPath, 'preferences.json');
return pfs.dirExists(dirPath).then(exists => {
if (exists) {
let snippetsPromise = this.getEmmetCustomization(snippetsPath).then(value => LazyEmmet.snippetsFromFile = value);
let profilesPromise = this.getEmmetCustomization(syntaxProfilesPath).then(value => LazyEmmet.syntaxProfilesFromFile = value);
let preferencesPromise = this.getEmmetCustomization(preferencesPath).then(value => LazyEmmet.preferencesFromFile = value);
return TPromise.join([snippetsPromise, profilesPromise, preferencesPromise]);
}
this._messageService.show(Severity.Error, `The path set in emmet.extensionsPath "${LazyEmmet.extensionsPath}" does not exist.`);
return undefined;
});
}
}
return TPromise.as(void 0);
}
private getEmmetCustomization(filePath: string): TPromise<any> {
return pfs.fileExists(filePath).then(fileExists => {
if (fileExists) {
return pfs.readFile(filePath).then(buff => {
let parsedData = {};
try {
parsedData = JSON.parse(buff.toString());
} catch (err) {
this._messageService.show(Severity.Error, `Error while parsing "${filePath}": ${err}`);
}
return parsedData;
});
}
return {};
});
}
private _withEmmetPreferences(configurationService: IConfigurationService,
telemetryService: ITelemetryService,
_emmet: typeof emmet,
callback: (_emmet: typeof emmet) => void): void {
this.updateEmmetPreferences(configurationService, telemetryService, _emmet).then(() => {
try {
callback(_emmet);
} finally {
_emmet.resetUserData();
}
});
}
}
export class EmmetActionContext {
editor: ICommonCodeEditor;
emmet: typeof emmet;
editorAccessor: EditorAccessor;
constructor(editor: ICommonCodeEditor, _emmet: typeof emmet, editorAccessor: EditorAccessor) {
this.editor = editor;
this.emmet = _emmet;
this.editorAccessor = editorAccessor;
}
}
export interface IEmmetActionOptions extends IActionOptions {
actionName: string;
}
......@@ -248,11 +69,7 @@ export abstract class EmmetEditorAction extends EditorAction {
this.emmetActionName = opts.actionName;
}
abstract runEmmetAction(accessor: ServicesAccessor, ctx: EmmetActionContext);
protected noExpansionOccurred(editor: ICommonCodeEditor) {
// default do nothing
}
private static readonly emmetSupportedModes = ['html', 'css', 'xml', 'xsl', 'haml', 'jade', 'jsx', 'slim', 'scss', 'sass', 'less', 'stylus', 'styl', 'svg'];
private _lastGrammarContributions: TPromise<GrammarContributions> = null;
private _lastExtensionService: IExtensionService = null;
......@@ -267,81 +84,58 @@ export abstract class EmmetEditorAction extends EditorAction {
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): TPromise<void> {
const configurationService = accessor.get(IConfigurationService);
const instantiationService = accessor.get(IInstantiationService);
const extensionService = accessor.get(IExtensionService);
const modeService = accessor.get(IModeService);
const messageService = accessor.get(IMessageService);
const contextService = accessor.get(IWorkspaceContextService);
const workspaceRoot = contextService.hasWorkspace() ? contextService.getLegacyWorkspace().resource.fsPath : ''; // TODO@Ramya (https://github.com/Microsoft/vscode/issues/29244)
const telemetryService = accessor.get(ITelemetryService);
const commandService = accessor.get(ICommandService);
let mappedCommand = configurationService.getConfiguration<IEmmetConfiguration>().emmet.useNewEmmet ? this.actionMap[this.id] : undefined;
let mappedCommand = this.actionMap[this.id];
if (mappedCommand && mappedCommand !== 'emmet.expandAbbreviation' && mappedCommand !== 'emmet.wrapWithAbbreviation') {
return commandService.executeCommand<void>(mappedCommand);
}
return this._withGrammarContributions(extensionService).then((grammarContributions) => {
let editorAccessor = new EditorAccessor(
modeService,
editor,
configurationService.getConfiguration<IEmmetConfiguration>().emmet.syntaxProfiles,
configurationService.getConfiguration<IEmmetConfiguration>().emmet.excludeLanguages,
grammarContributions,
this.emmetActionName
);
if (mappedCommand === 'emmet.expandAbbreviation' || mappedCommand === 'emmet.wrapWithAbbreviation') {
return commandService.executeCommand<void>(mappedCommand, editorAccessor.getLanguage());
return commandService.executeCommand<void>(mappedCommand, EmmetEditorAction.getLanguage(modeService, editor, grammarContributions));
}
if (!editorAccessor.isEmmetEnabledMode()) {
this.noExpansionOccurred(editor);
return undefined;
}
return LazyEmmet.withConfiguredEmmet(configurationService, messageService, telemetryService, editorAccessor.getEmmetSupportedModes(), workspaceRoot, (_emmet) => {
if (!_emmet) {
this.noExpansionOccurred(editor);
return undefined;
}
editorAccessor.onBeforeEmmetAction();
instantiationService.invokeFunction((accessor) => {
this.runEmmetAction(accessor, new EmmetActionContext(editor, _emmet, editorAccessor));
});
editorAccessor.onAfterEmmetAction();
});
return undefined;
});
}
}
export class BasicEmmetEditorAction extends EmmetEditorAction {
public static getLanguage(languageIdentifierResolver: ILanguageIdentifierResolver, editor: ICommonCodeEditor, grammars: IGrammarContributions) {
let position = editor.getSelection().getStartPosition();
editor.getModel().forceTokenization(position.lineNumber);
let languageId = editor.getModel().getLanguageIdAtPosition(position.lineNumber, position.column);
let language = languageIdentifierResolver.getLanguageIdentifier(languageId).language;
let syntax = language.split('.').pop();
constructor(id: string, label: string, alias: string, actionName: string, kbOpts?: ICommandKeybindingsOptions) {
super({
id,
label,
alias,
precondition: EditorContextKeys.writable,
kbOpts,
actionName
});
}
public runEmmetAction(accessor: ServicesAccessor, ctx: EmmetActionContext) {
const telemetryService = accessor.get(ITelemetryService);
try {
if (!ctx.emmet.run(this.emmetActionName, ctx.editorAccessor)) {
this.noExpansionOccurred(ctx.editor);
} else if (this.emmetActionName === 'expand_abbreviation') {
telemetryService.publicLog('emmetActionSucceeded', { action: this.emmetActionName });
let checkParentMode = (): string => {
let languageGrammar = grammars.getGrammar(syntax);
if (!languageGrammar) {
return syntax;
}
} catch (err) {
this.noExpansionOccurred(ctx.editor);
}
let languages = languageGrammar.split('.');
if (languages.length < 2) {
return syntax;
}
for (let i = 1; i < languages.length; i++) {
const language = languages[languages.length - i];
if (this.emmetSupportedModes.indexOf(language) !== -1) {
return language;
}
}
return syntax;
};
return {
language: syntax,
parentMode: checkParentMode()
};
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { EditorAccessor, ILanguageIdentifierResolver, IGrammarContributions } from 'vs/workbench/parts/emmet/electron-browser/editorAccessor';
import { withMockCodeEditor } from 'vs/editor/test/common/mocks/mockCodeEditor';
import assert = require('assert');
import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
//
// To run the emmet tests only change .vscode/launch.json
// {
// "name": "Stacks Tests",
// "type": "node",
// "request": "launch",
// "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
// "stopOnEntry": false,
// "args": [
// "--timeout",
// "999999",
// "--colors",
// "-g",
// "Stacks" <<<--- Emmet
// ],
// Select the 'Stacks Tests' launch config and F5
//
class MockGrammarContributions implements IGrammarContributions {
private scopeName;
constructor(scopeName: string) {
this.scopeName = scopeName;
}
public getGrammar(mode: string): string {
return this.scopeName;
}
}
export interface IGrammarContributions {
getGrammar(mode: string): string;
}
suite('Emmet', () => {
test('emmet isEnabled', () => {
withMockCodeEditor([], {}, (editor) => {
function testIsEnabled(mode: string, scopeName: string, isEnabled = true, profile = {}, excluded = []) {
const languageIdentifier = new LanguageIdentifier(mode, 73);
const languageIdentifierResolver: ILanguageIdentifierResolver = {
getLanguageIdentifier: (languageId: LanguageId) => {
if (languageId === 73) {
return languageIdentifier;
}
throw new Error('Unexpected');
}
};
editor.getModel().setMode(languageIdentifier);
let editorAccessor = new EditorAccessor(languageIdentifierResolver, editor, profile, excluded, new MockGrammarContributions(scopeName));
assert.equal(editorAccessor.isEmmetEnabledMode(), isEnabled);
}
// emmet supported languages, null is used as the scopeName since it should not be consulted, they map to to mode to the same syntax name
let emmetSupportedModes = ['html', 'css', 'xml', 'xsl', 'haml', 'jade', 'jsx', 'slim', 'scss', 'sass', 'less', 'stylus', 'styl'];
emmetSupportedModes.forEach(each => {
testIsEnabled(each, null);
});
// mapped syntaxes
testIsEnabled('typescriptreact', null);
testIsEnabled('javascriptreact', null);
testIsEnabled('jsx-tags', null);
testIsEnabled('sass-indented', null);
// syntaxes mapped using the scope name of the grammar
testIsEnabled('markdown', 'text.html.markdown');
testIsEnabled('handlebars', 'text.html.handlebars');
testIsEnabled('nunjucks', 'text.html.nunjucks');
testIsEnabled('laravel-blade', 'text.html.php.laravel-blade');
// languages that have different Language Id and scopeName
testIsEnabled('razor', 'text.html.cshtml');
testIsEnabled('HTML (Eex)', 'text.html.elixir');
// not enabled syntaxes
testIsEnabled('java', 'source.java', false);
testIsEnabled('javascript', 'source.js', false);
// enabled syntax with user configured setting
testIsEnabled('java', 'source.java', true, {
'java': 'html'
});
});
withMockCodeEditor([
'<?'
], {}, (editor) => {
function testIsEnabled(mode: string, scopeName: string, isEnabled = true, profile = {}, excluded = []) {
const languageIdentifier = new LanguageIdentifier(mode, 73);
const languageIdentifierResolver: ILanguageIdentifierResolver = {
getLanguageIdentifier: (languageId: LanguageId) => {
if (languageId === 73) {
return languageIdentifier;
}
throw new Error('Unexpected');
}
};
editor.getModel().setMode(languageIdentifier);
editor.setPosition({ lineNumber: 1, column: 3 });
let editorAccessor = new EditorAccessor(languageIdentifierResolver, editor, profile, excluded, new MockGrammarContributions(scopeName));
assert.equal(editorAccessor.isEmmetEnabledMode(), isEnabled);
}
// emmet enabled language that is disabled
testIsEnabled('php', 'text.html.php', false, {}, ['php']);
});
});
test('emmet syntax profiles', () => {
withMockCodeEditor([], {}, (editor) => {
function testSyntax(mode: string, scopeName: string, expectedSyntax: string, profile = {}, excluded = []) {
const languageIdentifier = new LanguageIdentifier(mode, 73);
const languageIdentifierResolver: ILanguageIdentifierResolver = {
getLanguageIdentifier: (languageId: LanguageId) => {
if (languageId === 73) {
return languageIdentifier;
}
throw new Error('Unexpected');
}
};
editor.getModel().setMode(languageIdentifier);
let editorAccessor = new EditorAccessor(languageIdentifierResolver, editor, profile, excluded, new MockGrammarContributions(scopeName));
assert.equal(editorAccessor.getSyntax(), expectedSyntax);
}
// emmet supported languages, null is used as the scopeName since it should not be consulted, they map to to mode to the same syntax name
let emmetSupportedModes = ['html', 'css', 'xml', 'xsl', 'haml', 'jade', 'jsx', 'slim', 'scss', 'sass', 'less', 'stylus', 'styl'];
emmetSupportedModes.forEach(each => {
testSyntax(each, null, each);
});
// mapped syntaxes
testSyntax('typescriptreact', null, 'jsx');
testSyntax('javascriptreact', null, 'jsx');
testSyntax('jsx-tags', null, 'jsx');
testSyntax('sass-indented', null, 'sass');
// syntaxes mapped using the scope name of the grammar
testSyntax('markdown', 'text.html.markdown', 'html');
testSyntax('handlebars', 'text.html.handlebars', 'html');
testSyntax('nunjucks', 'text.html.nunjucks', 'html');
testSyntax('laravel-blade', 'text.html.php.laravel-blade', 'html');
// languages that have different Language Id and scopeName
testSyntax('razor', 'text.html.cshtml', 'html');
testSyntax('HTML (Eex)', 'text.html.elixir', 'html');
// user define mapping
testSyntax('java', 'source.java', 'html', {
'java': 'html'
});
});
});
test('emmet replace range', () => {
withMockCodeEditor(['This is line 1'], {}, (editor) => {
let editorAccessor = new EditorAccessor(null, editor, null, [], new MockGrammarContributions(''), 'expand_abbreviation');
editor.getModel().setValue('This is line 1');
assert.equal(editorAccessor.getRangeToReplace('line', 8, 12), null);
editor.getModel().setValue('This is line 1');
assert.equal(editorAccessor.getRangeToReplace('line${0}', 8, 12), null);
editor.getModel().setValue('This is line 1');
let range = editorAccessor.getRangeToReplace('some other text', 8, 12);
assert.equal(range.startLineNumber, 1);
assert.equal(range.endLineNumber, 1);
assert.equal(range.startColumn, 9);
assert.equal(range.endColumn, 13);
});
});
});
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IGrammarContributions, ILanguageIdentifierResolver, EmmetEditorAction } from 'vs/workbench/parts/emmet/electron-browser/emmetActions';
import { withMockCodeEditor } from 'vs/editor/test/common/mocks/mockCodeEditor';
import assert = require('assert');
import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
//
// To run the emmet tests only change .vscode/launch.json
// {
// "name": "Stacks Tests",
// "type": "node",
// "request": "launch",
// "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
// "stopOnEntry": false,
// "args": [
// "--timeout",
// "999999",
// "--colors",
// "-g",
// "Stacks" <<<--- Emmet
// ],
// Select the 'Stacks Tests' launch config and F5
//
class MockGrammarContributions implements IGrammarContributions {
private scopeName;
constructor(scopeName: string) {
this.scopeName = scopeName;
}
public getGrammar(mode: string): string {
return this.scopeName;
}
}
suite('Emmet', () => {
test('Get language mode and parent mode for emmet', () => {
withMockCodeEditor([], {}, (editor) => {
function testIsEnabled(mode: string, scopeName: string, expectedLanguage?: string, expectedParentLanguage?: string) {
const languageIdentifier = new LanguageIdentifier(mode, 73);
const languageIdentifierResolver: ILanguageIdentifierResolver = {
getLanguageIdentifier: (languageId: LanguageId) => {
if (languageId === 73) {
return languageIdentifier;
}
throw new Error('Unexpected');
}
};
editor.getModel().setMode(languageIdentifier);
let langOutput = EmmetEditorAction.getLanguage(languageIdentifierResolver, editor, new MockGrammarContributions(scopeName));
assert.equal(langOutput.language, expectedLanguage);
assert.equal(langOutput.parentMode, expectedParentLanguage);
}
// syntaxes mapped using the scope name of the grammar
testIsEnabled('markdown', 'text.html.markdown', 'markdown', 'html');
testIsEnabled('handlebars', 'text.html.handlebars', 'handlebars', 'html');
testIsEnabled('nunjucks', 'text.html.nunjucks', 'nunjucks', 'html');
testIsEnabled('laravel-blade', 'text.html.php.laravel-blade', 'laravel-blade', 'html');
// languages that have different Language Id and scopeName
// testIsEnabled('razor', 'text.html.cshtml', 'razor', 'html');
// testIsEnabled('HTML (Eex)', 'text.html.elixir', 'boo', 'html');
});
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册