blob: f528ad81a042a86f1cd093ac276d2f9c9f0e7a6d [file] [log] [blame]
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
import { DocumentHighlightKind, Location, Range, SymbolKind, TextEdit, FileType } from '../cssLanguageTypes';
import * as nls from 'vscode-nls';
import * as nodes from '../parser/cssNodes';
import { Symbols } from '../parser/cssSymbolScope';
import { getColorValue, hslFromColor } from '../languageFacts/facts';
import { startsWith } from '../utils/strings';
import { dirname, joinPath } from '../utils/resources';
var localize = nls.loadMessageBundle();
var CSSNavigation = /** @class */ (function () {
function CSSNavigation(fileSystemProvider) {
this.fileSystemProvider = fileSystemProvider;
}
CSSNavigation.prototype.findDefinition = function (document, position, stylesheet) {
var symbols = new Symbols(stylesheet);
var offset = document.offsetAt(position);
var node = nodes.getNodeAtOffset(stylesheet, offset);
if (!node) {
return null;
}
var symbol = symbols.findSymbolFromNode(node);
if (!symbol) {
return null;
}
return {
uri: document.uri,
range: getRange(symbol.node, document)
};
};
CSSNavigation.prototype.findReferences = function (document, position, stylesheet) {
var highlights = this.findDocumentHighlights(document, position, stylesheet);
return highlights.map(function (h) {
return {
uri: document.uri,
range: h.range
};
});
};
CSSNavigation.prototype.findDocumentHighlights = function (document, position, stylesheet) {
var result = [];
var offset = document.offsetAt(position);
var node = nodes.getNodeAtOffset(stylesheet, offset);
if (!node || node.type === nodes.NodeType.Stylesheet || node.type === nodes.NodeType.Declarations) {
return result;
}
if (node.type === nodes.NodeType.Identifier && node.parent && node.parent.type === nodes.NodeType.ClassSelector) {
node = node.parent;
}
var symbols = new Symbols(stylesheet);
var symbol = symbols.findSymbolFromNode(node);
var name = node.getText();
stylesheet.accept(function (candidate) {
if (symbol) {
if (symbols.matchesSymbol(candidate, symbol)) {
result.push({
kind: getHighlightKind(candidate),
range: getRange(candidate, document)
});
return false;
}
}
else if (node && node.type === candidate.type && candidate.matches(name)) {
// Same node type and data
result.push({
kind: getHighlightKind(candidate),
range: getRange(candidate, document)
});
}
return true;
});
return result;
};
CSSNavigation.prototype.isRawStringDocumentLinkNode = function (node) {
return node.type === nodes.NodeType.Import;
};
CSSNavigation.prototype.findDocumentLinks = function (document, stylesheet, documentContext) {
var links = this.findUnresolvedLinks(document, stylesheet);
for (var i = 0; i < links.length; i++) {
var target = links[i].target;
if (target && !(/^\w+:\/\//g.test(target))) {
var resolved = documentContext.resolveReference(target, document.uri);
if (resolved) {
links[i].target = resolved;
}
}
}
return links;
};
CSSNavigation.prototype.findDocumentLinks2 = function (document, stylesheet, documentContext) {
return __awaiter(this, void 0, void 0, function () {
var links, resolvedLinks, _i, links_1, link, target, resolvedTarget;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
links = this.findUnresolvedLinks(document, stylesheet);
resolvedLinks = [];
_i = 0, links_1 = links;
_a.label = 1;
case 1:
if (!(_i < links_1.length)) return [3 /*break*/, 5];
link = links_1[_i];
target = link.target;
if (!(target && !(/^\w+:\/\//g.test(target)))) return [3 /*break*/, 3];
return [4 /*yield*/, this.resolveRelativeReference(target, document.uri, documentContext)];
case 2:
resolvedTarget = _a.sent();
if (resolvedTarget !== undefined) {
link.target = resolvedTarget;
resolvedLinks.push(link);
}
return [3 /*break*/, 4];
case 3:
resolvedLinks.push(link);
_a.label = 4;
case 4:
_i++;
return [3 /*break*/, 1];
case 5: return [2 /*return*/, resolvedLinks];
}
});
});
};
CSSNavigation.prototype.findUnresolvedLinks = function (document, stylesheet) {
var _this = this;
var result = [];
var collect = function (uriStringNode) {
var rawUri = uriStringNode.getText();
var range = getRange(uriStringNode, document);
// Make sure the range is not empty
if (range.start.line === range.end.line && range.start.character === range.end.character) {
return;
}
if (startsWith(rawUri, "'") || startsWith(rawUri, "\"")) {
rawUri = rawUri.slice(1, -1);
}
result.push({ target: rawUri, range: range });
};
stylesheet.accept(function (candidate) {
if (candidate.type === nodes.NodeType.URILiteral) {
var first = candidate.getChild(0);
if (first) {
collect(first);
}
return false;
}
/**
* In @import, it is possible to include links that do not use `url()`
* For example, `@import 'foo.css';`
*/
if (candidate.parent && _this.isRawStringDocumentLinkNode(candidate.parent)) {
var rawText = candidate.getText();
if (startsWith(rawText, "'") || startsWith(rawText, "\"")) {
collect(candidate);
}
return false;
}
return true;
});
return result;
};
CSSNavigation.prototype.findDocumentSymbols = function (document, stylesheet) {
var result = [];
stylesheet.accept(function (node) {
var entry = {
name: null,
kind: SymbolKind.Class,
location: null
};
var locationNode = node;
if (node instanceof nodes.Selector) {
entry.name = node.getText();
locationNode = node.findAParent(nodes.NodeType.Ruleset, nodes.NodeType.ExtendsReference);
if (locationNode) {
entry.location = Location.create(document.uri, getRange(locationNode, document));
result.push(entry);
}
return false;
}
else if (node instanceof nodes.VariableDeclaration) {
entry.name = node.getName();
entry.kind = SymbolKind.Variable;
}
else if (node instanceof nodes.MixinDeclaration) {
entry.name = node.getName();
entry.kind = SymbolKind.Method;
}
else if (node instanceof nodes.FunctionDeclaration) {
entry.name = node.getName();
entry.kind = SymbolKind.Function;
}
else if (node instanceof nodes.Keyframe) {
entry.name = localize('literal.keyframes', "@keyframes {0}", node.getName());
}
else if (node instanceof nodes.FontFace) {
entry.name = localize('literal.fontface', "@font-face");
}
else if (node instanceof nodes.Media) {
var mediaList = node.getChild(0);
if (mediaList instanceof nodes.Medialist) {
entry.name = '@media ' + mediaList.getText();
entry.kind = SymbolKind.Module;
}
}
if (entry.name) {
entry.location = Location.create(document.uri, getRange(locationNode, document));
result.push(entry);
}
return true;
});
return result;
};
CSSNavigation.prototype.findDocumentColors = function (document, stylesheet) {
var result = [];
stylesheet.accept(function (node) {
var colorInfo = getColorInformation(node, document);
if (colorInfo) {
result.push(colorInfo);
}
return true;
});
return result;
};
CSSNavigation.prototype.getColorPresentations = function (document, stylesheet, color, range) {
var result = [];
var red256 = Math.round(color.red * 255), green256 = Math.round(color.green * 255), blue256 = Math.round(color.blue * 255);
var label;
if (color.alpha === 1) {
label = "rgb(" + red256 + ", " + green256 + ", " + blue256 + ")";
}
else {
label = "rgba(" + red256 + ", " + green256 + ", " + blue256 + ", " + color.alpha + ")";
}
result.push({ label: label, textEdit: TextEdit.replace(range, label) });
if (color.alpha === 1) {
label = "#" + toTwoDigitHex(red256) + toTwoDigitHex(green256) + toTwoDigitHex(blue256);
}
else {
label = "#" + toTwoDigitHex(red256) + toTwoDigitHex(green256) + toTwoDigitHex(blue256) + toTwoDigitHex(Math.round(color.alpha * 255));
}
result.push({ label: label, textEdit: TextEdit.replace(range, label) });
var hsl = hslFromColor(color);
if (hsl.a === 1) {
label = "hsl(" + hsl.h + ", " + Math.round(hsl.s * 100) + "%, " + Math.round(hsl.l * 100) + "%)";
}
else {
label = "hsla(" + hsl.h + ", " + Math.round(hsl.s * 100) + "%, " + Math.round(hsl.l * 100) + "%, " + hsl.a + ")";
}
result.push({ label: label, textEdit: TextEdit.replace(range, label) });
return result;
};
CSSNavigation.prototype.doRename = function (document, position, newName, stylesheet) {
var _a;
var highlights = this.findDocumentHighlights(document, position, stylesheet);
var edits = highlights.map(function (h) { return TextEdit.replace(h.range, newName); });
return {
changes: (_a = {}, _a[document.uri] = edits, _a)
};
};
CSSNavigation.prototype.resolveRelativeReference = function (ref, documentUri, documentContext) {
return __awaiter(this, void 0, void 0, function () {
var moduleName, rootFolderUri, documentFolderUri, modulePath, pathWithinModule;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!(ref[0] === '~' && ref[1] !== '/' && this.fileSystemProvider)) return [3 /*break*/, 3];
ref = ref.substring(1);
if (!startsWith(documentUri, 'file://')) return [3 /*break*/, 2];
moduleName = getModuleNameFromPath(ref);
rootFolderUri = documentContext.resolveReference('/', documentUri);
documentFolderUri = dirname(documentUri);
return [4 /*yield*/, this.resolvePathToModule(moduleName, documentFolderUri, rootFolderUri)];
case 1:
modulePath = _a.sent();
if (modulePath) {
pathWithinModule = ref.substring(moduleName.length + 1);
return [2 /*return*/, joinPath(modulePath, pathWithinModule)];
}
_a.label = 2;
case 2: return [2 /*return*/, documentContext.resolveReference(ref, documentUri)];
case 3: return [2 /*return*/, documentContext.resolveReference(ref, documentUri)];
}
});
});
};
CSSNavigation.prototype.resolvePathToModule = function (_moduleName, documentFolderUri, rootFolderUri) {
return __awaiter(this, void 0, void 0, function () {
var packPath;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
packPath = joinPath(documentFolderUri, 'node_modules', _moduleName, 'package.json');
return [4 /*yield*/, this.fileExists(packPath)];
case 1:
if (_a.sent()) {
return [2 /*return*/, dirname(packPath)];
}
else if (rootFolderUri && documentFolderUri.startsWith(rootFolderUri) && (documentFolderUri.length !== rootFolderUri.length)) {
return [2 /*return*/, this.resolvePathToModule(_moduleName, dirname(documentFolderUri), rootFolderUri)];
}
return [2 /*return*/, undefined];
}
});
});
};
CSSNavigation.prototype.fileExists = function (uri) {
return __awaiter(this, void 0, void 0, function () {
var stat, err_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!this.fileSystemProvider) {
return [2 /*return*/, false];
}
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, this.fileSystemProvider.stat(uri)];
case 2:
stat = _a.sent();
if (stat.type === FileType.Unknown && stat.size === -1) {
return [2 /*return*/, false];
}
return [2 /*return*/, true];
case 3:
err_1 = _a.sent();
return [2 /*return*/, false];
case 4: return [2 /*return*/];
}
});
});
};
return CSSNavigation;
}());
export { CSSNavigation };
function getColorInformation(node, document) {
var color = getColorValue(node);
if (color) {
var range = getRange(node, document);
return { color: color, range: range };
}
return null;
}
function getRange(node, document) {
return Range.create(document.positionAt(node.offset), document.positionAt(node.end));
}
function getHighlightKind(node) {
if (node.type === nodes.NodeType.Selector) {
return DocumentHighlightKind.Write;
}
if (node instanceof nodes.Identifier) {
if (node.parent && node.parent instanceof nodes.Property) {
if (node.isCustomProperty) {
return DocumentHighlightKind.Write;
}
}
}
if (node.parent) {
switch (node.parent.type) {
case nodes.NodeType.FunctionDeclaration:
case nodes.NodeType.MixinDeclaration:
case nodes.NodeType.Keyframe:
case nodes.NodeType.VariableDeclaration:
case nodes.NodeType.FunctionParameter:
return DocumentHighlightKind.Write;
}
}
return DocumentHighlightKind.Read;
}
function toTwoDigitHex(n) {
var r = n.toString(16);
return r.length !== 2 ? '0' + r : r;
}
function getModuleNameFromPath(path) {
// If a scoped module (starts with @) then get up until second instance of '/', otherwise get until first instance of '/'
if (path[0] === '@') {
return path.substring(0, path.indexOf('/', path.indexOf('/') + 1));
}
return path.substring(0, path.indexOf('/'));
}