yaml-language-server
Version:
188 lines • 7.78 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.YamlRename = void 0;
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
const yaml_documents_1 = require("../parser/yaml-documents");
const arrUtils_1 = require("../utils/arrUtils");
const textBuffer_1 = require("../utils/textBuffer");
const yaml_1 = require("yaml");
const yamlAstUtils_1 = require("../utils/yamlAstUtils");
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
class YamlRename {
constructor(telemetry) {
this.telemetry = telemetry;
}
prepareRename(document, params) {
try {
const target = this.findTarget(document, params.position);
if (!target) {
return null;
}
if (!this.findAnchorToken(target.yamlDoc, target.anchorNode)) {
return null;
}
return this.getNameRange(document, target.token);
}
catch (err) {
this.telemetry?.sendError('yaml.prepareRename.error', err);
return null;
}
}
doRename(document, params) {
try {
const target = this.findTarget(document, params.position);
if (!target) {
return null;
}
const anchorToken = this.findAnchorToken(target.yamlDoc, target.anchorNode);
if (!anchorToken) {
return null;
}
const newName = params.newName;
const invalidChar = this.findInvalidAnchorChar(newName);
if (invalidChar !== null) {
throw new vscode_languageserver_protocol_1.ResponseError(vscode_languageserver_protocol_1.ErrorCodes.InvalidParams, `Anchor name cannot contain '${invalidChar}'`);
}
const edits = [];
edits.push(vscode_languageserver_types_1.TextEdit.replace(this.getNameRange(document, anchorToken), newName));
(0, yaml_1.visit)(target.yamlDoc.internalDocument, (key, node) => {
if ((0, yaml_1.isAlias)(node) && node.srcToken && node.resolve(target.yamlDoc.internalDocument) === target.anchorNode) {
edits.push(vscode_languageserver_types_1.TextEdit.replace(this.getNameRange(document, node.srcToken), newName));
}
});
return {
changes: {
[document.uri]: edits,
},
};
}
catch (err) {
if (err instanceof vscode_languageserver_protocol_1.ResponseError) {
throw err;
}
this.telemetry?.sendError('yaml.rename.error', err);
return null;
}
}
findTarget(document, position) {
const yamlDocuments = yaml_documents_1.yamlDocumentsCache.getYamlDocument(document);
const offset = document.offsetAt(position);
const yamlDoc = (0, arrUtils_1.matchOffsetToDocument)(offset, yamlDocuments);
if (!yamlDoc) {
return null;
}
const [node] = yamlDoc.getNodeFromPosition(offset, new textBuffer_1.TextBuffer(document));
if (!node) {
return this.findByToken(yamlDoc, offset);
}
if ((0, yaml_1.isAlias)(node) && node.srcToken && this.isOffsetInsideToken(node.srcToken, offset)) {
const anchorNode = node.resolve(yamlDoc.internalDocument);
if (!anchorNode) {
return null;
}
return { anchorNode, token: node.srcToken, yamlDoc };
}
if (((0, yaml_1.isCollection)(node) || (0, yaml_1.isScalar)(node)) && node.anchor) {
const anchorToken = this.findAnchorToken(yamlDoc, node);
if (anchorToken && this.isOffsetInsideToken(anchorToken, offset)) {
return { anchorNode: node, token: anchorToken, yamlDoc };
}
}
return this.findByToken(yamlDoc, offset);
}
findByToken(yamlDoc, offset) {
let target;
(0, yaml_1.visit)(yamlDoc.internalDocument, (key, node) => {
if ((0, yaml_1.isAlias)(node) && node.srcToken && this.isOffsetInsideToken(node.srcToken, offset)) {
const anchorNode = node.resolve(yamlDoc.internalDocument);
if (anchorNode) {
target = { anchorNode, token: node.srcToken, yamlDoc };
return yaml_1.visit.BREAK;
}
}
if (((0, yaml_1.isCollection)(node) || (0, yaml_1.isScalar)(node)) && node.anchor) {
const anchorToken = this.findAnchorToken(yamlDoc, node);
if (anchorToken && this.isOffsetInsideToken(anchorToken, offset)) {
target = { anchorNode: node, token: anchorToken, yamlDoc };
return yaml_1.visit.BREAK;
}
}
});
return target ?? null;
}
findAnchorToken(yamlDoc, node) {
const parent = yamlDoc.getParent(node);
const candidates = [];
if (parent && parent.srcToken) {
candidates.push(parent.srcToken);
}
if (node.srcToken) {
candidates.push(node.srcToken);
}
for (const token of candidates) {
const anchor = this.getAnchorFromToken(token, node);
if (anchor) {
return anchor;
}
}
return undefined;
}
getAnchorFromToken(token, node) {
if ((0, yamlAstUtils_1.isCollectionItem)(token)) {
return this.getAnchorFromCollectionItem(token);
}
else if (yaml_1.CST.isCollection(token)) {
const collection = token;
for (const item of collection.items ?? []) {
if (item.value !== node.srcToken) {
continue;
}
const anchor = this.getAnchorFromCollectionItem(item);
if (anchor) {
return anchor;
}
}
}
return undefined;
}
getAnchorFromCollectionItem(token) {
for (const t of token.start) {
if (t.type === 'anchor') {
return t;
}
}
if (token.sep && Array.isArray(token.sep)) {
for (const t of token.sep) {
if (t.type === 'anchor') {
return t;
}
}
}
return undefined;
}
getNameRange(document, token) {
const startOffset = token.offset + 1;
const endOffset = token.offset + token.source.length;
return vscode_languageserver_types_1.Range.create(document.positionAt(startOffset), document.positionAt(endOffset));
}
isOffsetInsideToken(token, offset) {
return offset >= token.offset && offset <= token.offset + token.source.length;
}
findInvalidAnchorChar(name) {
// YAML 1.2.2 spec: anchor names cannot contain flow indicators or whitespace
// https://yaml.org/spec/1.2.2/#rule-ns-anchor-char
const invalidChars = ['[', ']', '{', '}', ',', ' ', '\t'];
for (const char of invalidChars) {
if (name.includes(char)) {
return char === ' ' ? 'space' : char === '\t' ? 'tab' : char;
}
}
return null;
}
}
exports.YamlRename = YamlRename;
//# sourceMappingURL=yamlRename.js.map