UNPKG

@inst/vscode-bin-darwin

Version:

BINARY ONLY - VSCode binary deployment for macOS

657 lines (656 loc) 27.9 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); const vscode_languageserver_types_1 = require("vscode-languageserver-types"); const expand_full_1 = require("./expand/expand-full"); const extract = require("@emmetio/extract-abbreviation"); const path = require("path"); const fs = require("fs"); const snippetKeyCache = new Map(); let markupSnippetKeys; let markupSnippetKeysRegex; const stylesheetCustomSnippetsKeyCache = new Map(); const htmlAbbreviationStartRegex = /^[a-z,A-Z,!,(,[,#,\.]/; const htmlAbbreviationEndRegex = /[a-z,A-Z,!,),\],#,\.,},\d,*,$]$/; const cssAbbreviationRegex = /^[a-z,A-Z,!,@,#]/; const emmetModes = ['html', 'pug', 'slim', 'haml', 'xml', 'xsl', 'jsx', 'css', 'scss', 'sass', 'less', 'stylus']; const commonlyUsedTags = ['div', 'span', 'p', 'b', 'i', 'body', 'html', 'ul', 'ol', 'li', 'head', 'section', 'canvas', 'dl', 'dt', 'dd', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'footer', 'nav', 'aside', 'table', 'tbody', 'thead', 'tfoot', 'tr', 'th', 'td', 'blockquote', 'pre', 'sup', 'sub', 'title', 'plaintext', 'noscript', 'legend', 'u', 'code', 'comment', 'caption', 'colgroup', 'button', 'big', 'applet', 'address', 'strong', 'small']; const bemFilterSuffix = 'bem'; const filterDelimitor = '|'; const trimFilterSuffix = 't'; const commentFilterSuffix = 'c'; const defaultUnitAliases = { e: 'em', p: '%', x: 'ex', r: 'rem' }; function doComplete(document, position, syntax, emmetConfig) { if (emmetConfig.showExpandedAbbreviation === 'never' || emmetModes.indexOf(syntax) === -1) { return; } if (!isStyleSheet(syntax)) { if (!snippetKeyCache.has(syntax) || !markupSnippetKeysRegex || markupSnippetKeysRegex.length === 0) { let registry = customSnippetRegistry[syntax] ? customSnippetRegistry[syntax] : expand_full_1.createSnippetsRegistry(syntax); if (!snippetKeyCache.has(syntax)) { snippetKeyCache.set(syntax, registry.all({ type: 'string' }).map(snippet => { return snippet.key; })); } markupSnippetKeysRegex = registry.all({ type: 'regexp' }).map(snippet => { return snippet.key; }); } markupSnippetKeys = snippetKeyCache.get(syntax); } let extractedValue = extractAbbreviation(document, position); if (!extractedValue) { return vscode_languageserver_types_1.CompletionList.create([], true); } let { abbreviationRange, abbreviation, filter } = extractedValue; let currentLineTillPosition = getCurrentLine(document, position).substr(0, position.character); let currentWord = getCurrentWord(currentLineTillPosition); // Dont attempt to expand open tags if (currentWord === abbreviation && currentLineTillPosition.endsWith(`<${abbreviation}`) && (syntax === 'html' || syntax === 'xml' || syntax === 'xsl' || syntax === 'jsx')) { return vscode_languageserver_types_1.CompletionList.create([], true); } let expandOptions = getExpandOptions(syntax, emmetConfig, filter); let expandedText; let expandedAbbr; let completionItems = []; // If abbreviation is valid, then expand it and ensure the expanded value is not noise if (isAbbreviationValid(syntax, abbreviation)) { try { expandedText = expand_full_1.expand(abbreviation, expandOptions); } catch (e) { } if (expandedText && isExpandedTextNoise(syntax, abbreviation, expandedText)) { expandedText = ''; } } // Create completion item for expanded abbreviation if (expandedText) { expandedAbbr = vscode_languageserver_types_1.CompletionItem.create(abbreviation); expandedAbbr.textEdit = vscode_languageserver_types_1.TextEdit.replace(abbreviationRange, escapeNonTabStopDollar(expandedText)); expandedAbbr.documentation = replaceTabStopsWithCursors(expandedText); expandedAbbr.insertTextFormat = vscode_languageserver_types_1.InsertTextFormat.Snippet; expandedAbbr.detail = 'Emmet Abbreviation'; if (filter === 'bem' || filter === 'c') { expandedAbbr.label = abbreviation + filterDelimitor + (filter === 'bem' ? bemFilterSuffix : commentFilterSuffix); } completionItems = [expandedAbbr]; } if (isStyleSheet(syntax)) { if (!expandedText) { return vscode_languageserver_types_1.CompletionList.create([], true); } const stylesheetCustomSnippetsKeys = stylesheetCustomSnippetsKeyCache.has(syntax) ? stylesheetCustomSnippetsKeyCache.get(syntax) : stylesheetCustomSnippetsKeyCache.get('css'); completionItems = makeSnippetSuggestion(stylesheetCustomSnippetsKeys, currentWord, abbreviation, abbreviationRange, expandOptions, 'Emmet Custom Snippet', false); if (!completionItems.find(x => x.textEdit.newText === expandedAbbr.textEdit.newText)) { // Fix for https://github.com/Microsoft/vscode/issues/28933#issuecomment-309236902 // When user types in propertyname, emmet uses it to match with snippet names, resulting in width -> widows or font-family -> font: fantasy // Updating the label will update the filterText used by VS Code, thus filtering out such cases expandedAbbr.label = removeTabStops(expandedText); let abbreviationUsesUnitAliases = false; let abbreviationUsesNumberRange = /(\d+)-(\d+)$/.test(abbreviation); let m = abbreviation.match(/(\d+)([a-z])$/); if (m && !abbreviationUsesNumberRange) { let after = (syntax === 'sass' || syntax === 'stylus') ? '' : ';'; let unitName = m[2]; let unitValue = defaultUnitAliases[unitName]; const formatter = getFormatters(syntax, emmetConfig.preferences); if (formatter && formatter['stylesheet']) { after = formatter['stylesheet']['after'] || after; if (formatter['stylesheet']['unitAliases'] && formatter['stylesheet']['unitAliases'][unitName]) { unitValue = formatter['stylesheet']['unitAliases'][unitName]; } } abbreviationUsesUnitAliases = unitValue && expandedText.endsWith(m[1] + unitValue + after); } // Fix for https://github.com/Microsoft/vscode/issues/33898 and // https://github.com/Microsoft/vscode/issues/32277#issuecomment-321836737 if (abbreviationUsesUnitAliases || abbreviationUsesNumberRange) { expandedAbbr.filterText = abbreviation; } completionItems.push(expandedAbbr); } return vscode_languageserver_types_1.CompletionList.create(completionItems, true); } let commonlyUsedTagSuggestions = makeSnippetSuggestion(commonlyUsedTags, currentWord, abbreviation, abbreviationRange, expandOptions, 'Emmet Abbreviation'); completionItems = completionItems.concat(commonlyUsedTagSuggestions); if (emmetConfig.showAbbreviationSuggestions) { let abbreviationSuggestions = makeSnippetSuggestion(markupSnippetKeys, currentWord, abbreviation, abbreviationRange, expandOptions, 'Emmet Abbreviation'); // Workaround for the main expanded abbr not appearing before the snippet suggestions if (expandedAbbr && abbreviationSuggestions.length > 0) { expandedAbbr.sortText = '0' + expandedAbbr.label; } abbreviationSuggestions.forEach(item => { // Workaround for snippet suggestions items getting filtered out as the complete abbr does not start with snippetKey item.filterText = abbreviation; // Workaround for the main expanded abbr not appearing before the snippet suggestions item.sortText = '9' + abbreviation; }); completionItems = completionItems.concat(abbreviationSuggestions); } return vscode_languageserver_types_1.CompletionList.create(completionItems, true); } exports.doComplete = doComplete; function makeSnippetSuggestion(snippets, prefix, abbreviation, abbreviationRange, expandOptions, snippetDetail, skipFullMatch = true) { if (!prefix || !snippets) { return []; } let snippetCompletions = []; snippets.forEach(snippetKey => { if (!snippetKey.startsWith(prefix.toLowerCase()) || (skipFullMatch && snippetKey === prefix.toLowerCase())) { return; } let currentAbbr = abbreviation + snippetKey.substr(prefix.length); let expandedAbbr; try { expandedAbbr = expand_full_1.expand(currentAbbr, expandOptions); } catch (e) { } if (!expandedAbbr) { return; } let item = vscode_languageserver_types_1.CompletionItem.create(prefix + snippetKey.substr(prefix.length)); item.documentation = replaceTabStopsWithCursors(expandedAbbr); item.detail = snippetDetail; item.textEdit = vscode_languageserver_types_1.TextEdit.replace(abbreviationRange, escapeNonTabStopDollar(expandedAbbr)); item.insertTextFormat = vscode_languageserver_types_1.InsertTextFormat.Snippet; snippetCompletions.push(item); }); return snippetCompletions; } function getCurrentWord(currentLineTillPosition) { if (currentLineTillPosition) { let matches = currentLineTillPosition.match(/[\w,:,-]*$/); if (matches) { return matches[0]; } } } function replaceTabStopsWithCursors(expandedWord) { return expandedWord.replace(/([^\\])\$\{\d+\}/g, '$1|').replace(/\$\{\d+:([^\}]+)\}/g, '$1'); } function removeTabStops(expandedWord) { return expandedWord.replace(/([^\\])\$\{\d+\}/g, '$1').replace(/\$\{\d+:([^\}]+)\}/g, '$1'); } function escapeNonTabStopDollar(text) { return text ? text.replace(/([^\\])(\$)([^\{])/g, '$1\\$2$3') : text; } function getCurrentLine(document, position) { let offset = document.offsetAt(position); let text = document.getText(); let start = 0; let end = text.length; for (let i = offset - 1; i >= 0; i--) { if (text[i] === '\n') { start = i + 1; break; } } for (let i = offset; i < text.length; i++) { if (text[i] === '\n') { end = i; break; } } return text.substring(start, end); } let customSnippetRegistry = {}; let variablesFromFile = {}; let profilesFromFile = {}; exports.emmetSnippetField = (index, placeholder) => `\${${index}${placeholder ? ':' + placeholder : ''}}`; function isStyleSheet(syntax) { let stylesheetSyntaxes = ['css', 'scss', 'sass', 'less', 'stylus']; return (stylesheetSyntaxes.indexOf(syntax) > -1); } exports.isStyleSheet = isStyleSheet; /** * Extracts abbreviation from the given position in the given document */ function extractAbbreviation(document, position, lookAhead = true) { let filter; let pos = position.character; let currentLine = getCurrentLine(document, position); let currentLineTillPosition = currentLine.substr(0, position.character); let lengthOccupiedByFilter = 0; if (currentLineTillPosition.endsWith(`${filterDelimitor}${bemFilterSuffix}`)) { lengthOccupiedByFilter = 4; pos -= lengthOccupiedByFilter; filter = bemFilterSuffix; } else if (currentLineTillPosition.endsWith(`${filterDelimitor}${commentFilterSuffix}`)) { lengthOccupiedByFilter = 2; pos -= lengthOccupiedByFilter; filter = commentFilterSuffix; } let result; try { result = extract(currentLine, pos, lookAhead); } catch (e) { } if (!result) { return null; } let rangeToReplace = vscode_languageserver_types_1.Range.create(position.line, result.location, position.line, result.location + result.abbreviation.length + lengthOccupiedByFilter); return { abbreviationRange: rangeToReplace, abbreviation: result.abbreviation, filter }; } exports.extractAbbreviation = extractAbbreviation; function extractAbbreviationFromText(text) { let filter; if (!text) { return { abbreviation: '', filter }; } let pos = text.length; if (text.endsWith(`${filterDelimitor}${bemFilterSuffix}`)) { pos -= bemFilterSuffix.length + 1; filter = bemFilterSuffix; } else if (text.endsWith(`${filterDelimitor}${trimFilterSuffix}`)) { pos -= trimFilterSuffix.length + 1; filter = trimFilterSuffix; } else if (text.endsWith(`${filterDelimitor}${commentFilterSuffix}`)) { pos -= commentFilterSuffix.length + 1; filter = commentFilterSuffix; } let result; try { result = extract(text, pos, true); } catch (e) { } if (!result) { return null; } return { abbreviation: result.abbreviation, filter }; } exports.extractAbbreviationFromText = extractAbbreviationFromText; /** * Returns a boolean denoting validity of given abbreviation in the context of given syntax * Not needed once https://github.com/emmetio/atom-plugin/issues/22 is fixed * @param syntax string * @param abbreviation string */ function isAbbreviationValid(syntax, abbreviation) { if (!abbreviation) { return false; } if (isStyleSheet(syntax)) { // Fix for https://github.com/Microsoft/vscode/issues/1623 in new emmet if (abbreviation.endsWith(':')) { return false; } return cssAbbreviationRegex.test(abbreviation); } if (abbreviation.startsWith('!') && /[^!]/.test(abbreviation)) { return false; } // Its common for users to type (sometextinsidebrackets), this should not be treated as an abbreviation if (/^[a-z,A-Z,\d,-,:,\(,\),\.]*$/.test(abbreviation) && /\(/.test(abbreviation) && /\)/.test(abbreviation)) { return false; } return (htmlAbbreviationStartRegex.test(abbreviation) && htmlAbbreviationEndRegex.test(abbreviation)); } exports.isAbbreviationValid = isAbbreviationValid; function isExpandedTextNoise(syntax, abbreviation, expandedText) { // Unresolved css abbreviations get expanded to a blank property value // Eg: abc -> abc: ; or abc:d -> abc: d; which is noise if it gets suggested for every word typed if (isStyleSheet(syntax)) { let after = (syntax === 'sass' || syntax === 'stylus') ? '' : ';'; return expandedText === `${abbreviation}: \${1}${after}` || expandedText.replace(/\s/g, '') === abbreviation.replace(/\s/g, '') + after; } if (commonlyUsedTags.indexOf(abbreviation.toLowerCase()) > -1 || markupSnippetKeys.indexOf(abbreviation) > -1) { return false; } // Custom tags can have - or : if (/[-,:]/.test(abbreviation) && !/--|::/.test(abbreviation)) { return false; } // Its common for users to type some text and end it with period, this should not be treated as an abbreviation // Else it becomes noise. if (/^[a-z,A-Z,\d]*\.$/.test(abbreviation)) { return true; } // Unresolved html abbreviations get expanded as if it were a tag // Eg: abc -> <abc></abc> which is noise if it gets suggested for every word typed return expandedText.toLowerCase() === `<${abbreviation.toLowerCase()}>\${1}</${abbreviation.toLowerCase()}>`; } /** * Returns options to be used by the expand module * @param syntax * @param textToReplace */ function getExpandOptions(syntax, emmetConfig, filter) { emmetConfig = emmetConfig || {}; // Fetch snippet registry let baseSyntax = isStyleSheet(syntax) ? 'css' : 'html'; if (!customSnippetRegistry[syntax] && customSnippetRegistry[baseSyntax]) { customSnippetRegistry[syntax] = customSnippetRegistry[baseSyntax]; } // Fetch Profile let profile = getProfile(syntax, emmetConfig['syntaxProfiles']); let filtersFromProfile = (profile && profile['filters']) ? profile['filters'].split(',') : []; filtersFromProfile = filtersFromProfile.map(filterFromProfile => filterFromProfile.trim()); // Fetch Add Ons let addons = {}; if ((filter && filter === 'bem') || filtersFromProfile.indexOf('bem') > -1) { addons['bem'] = { element: '__' }; if (emmetConfig['preferences'] && emmetConfig['preferences']['bem.elementSeparator']) { addons['bem']['element'] = emmetConfig['preferences']['bem.elementSeparator']; } if (emmetConfig['preferences'] && emmetConfig['preferences']['bem.modifierSeparator']) { addons['bem']['modifier'] = emmetConfig['preferences']['bem.modifierSeparator']; } } if (syntax === 'jsx') { addons['jsx'] = true; } // Fetch Formatters let formatters = getFormatters(syntax, emmetConfig['preferences']); if ((filter && filter === 'c') || filtersFromProfile.indexOf('c') > -1) { if (!formatters['comment']) { formatters['comment'] = { enabled: true }; } else { formatters['comment']['enabled'] = true; } } return { field: exports.emmetSnippetField, syntax: syntax, profile: profile, addons: addons, variables: getVariables(emmetConfig['variables']), snippets: customSnippetRegistry[syntax], format: formatters }; } exports.getExpandOptions = getExpandOptions; /** * Expands given abbreviation using given options * @param abbreviation string * @param options */ function expandAbbreviation(abbreviation, options) { let expandedText = expand_full_1.expand(abbreviation, options); return escapeNonTabStopDollar(expandedText); } exports.expandAbbreviation = expandAbbreviation; /** * Maps and returns syntaxProfiles of previous format to ones compatible with new emmet modules * @param syntax */ function getProfile(syntax, profilesFromSettings) { if (!profilesFromSettings) { profilesFromSettings = {}; } let profilesConfig = Object.assign({}, profilesFromFile, profilesFromSettings); let options = profilesConfig[syntax]; if (!options || typeof options === 'string') { if (options === 'xhtml') { return { selfClosingStyle: 'xhtml' }; } return {}; } let newOptions = {}; for (let key in options) { switch (key) { case 'tag_case': newOptions['tagCase'] = (options[key] === 'lower' || options[key] === 'upper') ? options[key] : ''; break; case 'attr_case': newOptions['attributeCase'] = (options[key] === 'lower' || options[key] === 'upper') ? options[key] : ''; break; case 'attr_quotes': newOptions['attributeQuotes'] = options[key]; break; case 'tag_nl': newOptions['format'] = (options[key] === true || options[key] === false) ? options[key] : true; break; case 'inline_break': newOptions['inlineBreak'] = options[key]; break; case 'self_closing_tag': if (options[key] === true) { newOptions['selfClosingStyle'] = 'xml'; break; } if (options[key] === false) { newOptions['selfClosingStyle'] = 'html'; break; } newOptions['selfClosingStyle'] = options[key]; break; case 'compact_bool': newOptions['compactBooleanAttributes'] = options[key]; break; default: newOptions[key] = options[key]; break; } } return newOptions; } /** * Returns variables to be used while expanding snippets */ function getVariables(variablesFromSettings) { if (!variablesFromSettings) { return variablesFromFile; } return Object.assign({}, variablesFromFile, variablesFromSettings); } function getFormatters(syntax, preferences) { if (!preferences) { return {}; } if (!isStyleSheet(syntax)) { let commentFormatter = {}; for (let key in preferences) { switch (key) { case 'filter.commentAfter': commentFormatter['after'] = preferences[key]; break; case 'filter.commentBefore': commentFormatter['before'] = preferences[key]; break; case 'filter.commentTrigger': commentFormatter['trigger'] = preferences[key]; break; default: break; } } return { comment: commentFormatter }; } let stylesheetFormatter = { 'fuzzySearchMinScore': 0.3 }; for (let key in preferences) { switch (key) { case 'css.floatUnit': stylesheetFormatter['floatUnit'] = preferences[key]; break; case 'css.intUnit': stylesheetFormatter['intUnit'] = preferences[key]; break; case 'css.unitAliases': let unitAliases = {}; preferences[key].split(',').forEach(alias => { if (!alias || !alias.trim() || alias.indexOf(':') === -1) { return; } let aliasName = alias.substr(0, alias.indexOf(':')); let aliasValue = alias.substr(aliasName.length + 1); if (!aliasName.trim() || !aliasValue) { return; } unitAliases[aliasName.trim()] = aliasValue; }); stylesheetFormatter['unitAliases'] = unitAliases; break; case `${syntax}.valueSeparator`: stylesheetFormatter['between'] = preferences[key]; break; case `${syntax}.propertyEnd`: stylesheetFormatter['after'] = preferences[key]; break; default: break; } } return { stylesheet: stylesheetFormatter }; } /** * Updates customizations from snippets.json and syntaxProfiles.json files in the directory configured in emmet.extensionsPath setting */ function updateExtensionsPath(emmetExtensionsPath) { if (!emmetExtensionsPath || !emmetExtensionsPath.trim()) { resetSettingsFromFile(); return Promise.resolve(); } if (!path.isAbsolute(emmetExtensionsPath.trim())) { resetSettingsFromFile(); return Promise.reject('The path provided in emmet.extensionsPath setting should be absoulte path'); } if (!dirExists(emmetExtensionsPath.trim())) { resetSettingsFromFile(); return Promise.reject(`The directory ${emmetExtensionsPath.trim()} doesnt exist. Update emmet.extensionsPath setting`); } let dirPath = emmetExtensionsPath.trim(); let snippetsPath = path.join(dirPath, 'snippets.json'); let profilesPath = path.join(dirPath, 'syntaxProfiles.json'); let snippetsPromise = new Promise((resolve, reject) => { fs.readFile(snippetsPath, (err, snippetsData) => { if (err) { return reject(`Error while fetching the file ${snippetsPath}`); } try { let snippetsJson = JSON.parse(snippetsData.toString()); variablesFromFile = snippetsJson['variables']; customSnippetRegistry = {}; snippetKeyCache.clear(); Object.keys(snippetsJson).forEach(syntax => { if (!snippetsJson[syntax]['snippets']) { return; } let baseSyntax = isStyleSheet(syntax) ? 'css' : 'html'; let customSnippets = snippetsJson[syntax]['snippets']; if (snippetsJson[baseSyntax] && snippetsJson[baseSyntax]['snippets'] && baseSyntax !== syntax) { customSnippets = Object.assign({}, snippetsJson[baseSyntax]['snippets'], snippetsJson[syntax]['snippets']); } if (!isStyleSheet(syntax)) { // In Emmet 2.0 all snippets should be valid abbreviations // Convert old snippets that do not follow this format to new format for (let snippetKey in customSnippets) { if (customSnippets.hasOwnProperty(snippetKey) && customSnippets[snippetKey].startsWith('<') && customSnippets[snippetKey].endsWith('>')) { customSnippets[snippetKey] = `{${customSnippets[snippetKey]}}`; } } } else { stylesheetCustomSnippetsKeyCache.set(syntax, Object.keys(customSnippets)); } customSnippetRegistry[syntax] = expand_full_1.createSnippetsRegistry(syntax, customSnippets); let snippetKeys = customSnippetRegistry[syntax].all({ type: 'string' }).map(snippet => { return snippet.key; }); snippetKeyCache.set(syntax, snippetKeys); }); } catch (e) { return reject(`Error while parsing the file ${snippetsPath}`); } return resolve(); }); }); let variablesPromise = new Promise((resolve, reject) => { fs.readFile(profilesPath, (err, profilesData) => { try { if (!err) { profilesFromFile = JSON.parse(profilesData.toString()); } } catch (e) { } return resolve(); }); }); return Promise.all([snippetsPromise, variablesFromFile]).then(() => Promise.resolve()); } exports.updateExtensionsPath = updateExtensionsPath; function dirExists(dirPath) { try { return fs.statSync(dirPath).isDirectory(); } catch (e) { return false; } } function resetSettingsFromFile() { customSnippetRegistry = {}; snippetKeyCache.clear(); stylesheetCustomSnippetsKeyCache.clear(); profilesFromFile = {}; variablesFromFile = {}; } /** * Get the corresponding emmet mode for given vscode language mode * Eg: jsx for typescriptreact/javascriptreact or pug for jade * If the language is not supported by emmet or has been exlcuded via `exlcudeLanguages` setting, * then nothing is returned * * @param language * @param exlcudedLanguages Array of language ids that user has chosen to exlcude for emmet */ function getEmmetMode(language, excludedLanguages) { if (!language || excludedLanguages.indexOf(language) > -1) { return; } if (/\b(typescriptreact|javascriptreact|jsx-tags)\b/.test(language)) { return 'jsx'; } if (language === 'sass-indented') { return 'sass'; } if (language === 'jade') { return 'pug'; } if (emmetModes.indexOf(language) > -1) { return language; } } exports.getEmmetMode = getEmmetMode;